// $Id: THEMING.txt,v 1.7.2.2 2006/04/13 15:45:06 unconed Exp $

Much of the power of Views cannot be fully realized until an administrator
gets accustomed to theming.

Views offers the following themeables:

Theme Functions
===============

function theme_views_view_VIEWNAME($view, $type, $nodes);
  When Views attempts to display a view, first it attempts to call 
  theme('views_view_VIEWNAME'). If this function does not exist, then it will
  call theme('views_view'), which is the default method of displaying a view.
  As a themer, you can override either one, but in general it's better to
  override the specific one.

  The $view argument will be the full definition of the view from the database.

  The $type argument will be either 'block', 'page' or 'embed'. (It can actually
  be other things, but anything else will be user defined. More on this later). 
  This argument is used to control whether page or block specific display is 
  done.

  The $nodes argument will be an array with all of the information from the
  database nicely loaded.

function theme_views_summary_VIEWNAME($view, $type, $level, $nodes, $args);
  If a given view has a summary mode -- in which a listing of possible choices
  for arguments are given -- theme('views_view_summary_VIEWNAME') will be
  called instead. This is a very good choice to override, because views doesn't
  really give the admin many options for how to display this, and there
  are many different possibilities.

  $view contains the database view information.
  
  $type contains the same information as theme_views_view().

  $level is a number stating which argument this summary is for, starting from 
  0. If a view has multiple arguments (for example, view/archive/year/month/day) 
  the summary could be for the year (0), the month (1) or the day (2).

  $nodes will be an array of database information for the summary.

  $args will be the arguments actually present; in the above example will be
  an empty array if $level = 0, but will contain the year if $level = 1 and 
  the year and the month if $level = 2.

function theme_views_more($name);
  This simple theme function formats the 'more' link.

What a View Theme Should Do
===========================

At a bare minimum, a theme_views_view function needs to display some
information about each node. The internal one does quite a bit more
than that, and is broken up somewhat because different modes require
completely different display mechanisms. However, when theming a
view only one mode is likely to be used -- or in some cases, two.

First, a view should set the title:

  if ($type == 'page')
    // Done before theming so theme can change it if it wants.
    drupal_set_title(_views_get_title($view));

_views_get_title() will perform substitution so that %1, %2, et al get filled
from arguments. If your view doesn't have arguments, you can skip that step
and simply set the title to $views->title. Or set it to whatever you like! It
is your theme.

Next, a view theme should display the header.

  if ($view->header) {
    $header = check_markup($view->header, $view->header_format, false);
    $output = "<div class='view-header' id='view-header-$view->name'>$header</div>\n";
  }

The markup check is very important! It allows PHP code to run if that has been
set, and it sanitizes the output. Only skip that if you're sure the header code
is safe. You can do whatever you like with the DIV tag.

Next, a view needs to actually display the view. Views contains 3 support
functions for this:

    $output .= views_view_list($view, $nodes);
    $output .= views_view_table($view, $nodes);
    $output .= views_view_nodes($nodes, $teasers);

List Mode:

  A view in list mode displays each item as an entry in an Unordered List (<UL>).

    foreach ($nodes as $node) {
      $item = '';
      foreach ($view->field as $field) {
        if ($field['label'])
          $item .= "<div class='view-label' id='view-label-$field[queryname]'>" . $field['label'] . "</div>";
        $item .= "<div class='view-field' id='view-data-$field[queryname]'>" . _views_handle_field($fields, $field, $node) . "</div>";
      }
      $items[] = "<div class='view-item' id='view-item-$view->name'>$item</div>\n"; // l($node->title, "node/$node->nid");
    }
    if ($items)
      return theme('item_list', $items);

  The important piece of this code is that every field that the view is expected
  to display is in $view->field, which is an array containing a bunch of 
  information from the database. It cycles through this information, retrieves
  the associated field from the $node and adds it to the list. When loaded from
  the database, each field will be aliased in the form TABLE_FIELDNAME so as to
  avoid collisions. Doing anything fancy here requires a little bit of
  investigation to understand what data in the database is being retrieved.

  Note the call to _views_handle_field(). That allows modules to use callbacks
  to modify the data; the view doesn't necessarily display the exact data
  retrieved from the database. Your theme should use this unless you have a
  reason to display different data (such as you want to modify the data in
  a way that there is no callback handler for).

Table Mode:

  A view in table mode displays each item as a row in a table.

    $fields = _views_get_fields(); 

    foreach ($nodes as $node) {
      $row = array();
      foreach ($view->field as $field) {
        $cell['data'] = _views_handle_field($fields, $field, $node);
        $cell['class'] = 'view-field';
        $cell['id'] = "view-field-$field[queryname]";
        $row[] = $cell;
      }
      $rows[] = $row;
    }
    return theme('table', $view->table_header, $rows);

  As above, this code cycles through $view->field to retrieve the information
  and place it in a table row.

Node Mode:

  Node teasers and full nodes are the simplest of all:

    foreach ($nodes as $n) {
      $node = node_load($n->nid);
      $output .= node_view($node, $teasers, false, $links);
    }

  Because node mode doesn't add fields to the query, only the NID is really
  available, and each node is loaded individually.

Finally, a view theme should provide a MORE link in a block, if requested:

  if ($type == 'block' && $view->block_more && count($nodes) >= $view->nodes_per_block) {
    $output .= theme('views_more', $view->name); 
  }
  return "<div class='view' id='view-$view->name'>$output</div>\n";

What a View Summary Theme Should Do
===================================

First, a view summary should set the page title, if this is a 'page' view.

  if ($type == 'page') {
    $title = $view->argument[$level]['title'];
    if (!$title)
      $title = $view->title;
    drupal_set_title($title);
  }

Because each level of summary could have its own title (again, think to
the year/month/day example) there's a bit of searching around to find
the right title. If your view isn't this complex, feel free to brute
force it.

The next part is the most likely thing for anyone to want changed:

  $base = views_get_summary_link_base($view->argument[$level]['type'], $view->name, $level, $args);
  foreach ($nodes as $node) {
    $items[] = array(views_get_summary_link($view->argument[$level]['type'], $node, $base), $node->num_nodes);
  }
  if ($items)
    $output .= theme('table', array(), $items, array('id' => 'summary'));
  return "<div class='views' id='$view->name'>$output</div>";

This creates a simple table, with nothing more than a link to the next level
of the view, and the number of nodes that match. It is often desireable,
however, to print this as a list, rather than a table.

Examples
========

How to make block view behave differently from a url view
---------------------------------------------------------

Let's say you have a simple view that is something like 'newest posts in
with some taxonomy TERM.'. You want the view as both a page view and a 
block view, and you want the [more] link. No problem you say. But then
you discover that they both must be the same type -- but you really want
it to be a list view as a block, and a teaser view as a page. No problem.

First, set the view up as a List View by default. Tell it to include the
[more] link. We'll override its default behavior in page view.

Ordinarily, the [more] link only appears if it thinks there are actually
more entries to see; we don't want that behavior in this case because the
[more] link is going to different information.

function phptemplate_views_view_VIEWNAME($view, $type, $nodes) {

  if ($type == 'page')
    // Done before theming so theme can change it if it wants.
    drupal_set_title(_views_get_title($view));

  if ($view->header) {
    $header = check_markup($view->header, $view->header_format, false);
    $output = "<div class='view-header' id='view-header-$view->name'>$header</div>\n";
  }

  switch (type) {
    case 'block': // List View
      $output .= views_view_list($view, $nodes);
      break;
    case 'page': // Teaser View
      $output .= views_view_nodes($nodes, true);
      break;
  }

  if ($type == 'block') {
    $output .= theme('views_more', $view->name); 
  }
  return "<div class='view' id='view-$view->name'>$output</div>\n";
}

How to put embed two views on the same page
-------------------------------------------

In this view, the view takes an argument of the Node Title type,
set to 1 letter long. This means that views/example/A will give
all matching nodes that start with the letter A. What I really want,
though, is for the top of the view to contain the summary of the view
and for the bottom of the view to contain the current letter.

The easiest solution is to put the summary view in the header of
the view, but one MUST be careful here; embedding a view within a view
could lead to an infinite loop as it tries to replicate itself to 
infinity. Fortunately for us, the summary is themed separately from
the main view, and doesn't include header.

So what we'll do is put put a PHP snippet into the header of the view
and set the Input Filter to PHP code.

This view needs to have just one argument; it's possible to do this with
more arguments but you need to theme the summary as well, and check
where you actually are. Be sure that argument is set to provide a summary!

<?php

  // load a second copy of the view -- unfortunate but we don't have access
  // to the existing one.
  $view = views_get_view('VIEWNAME');

  // the array() indicates no arguments, which will provide the summary view.
  // We also don't want the pager for this view.
  print views_build_view('embed', $view, array(), false, false);
?>

See also the example of the comma separated list summary, which goes well with this.

How to provide a view which contains taxonomy headers
-----------------------------------------------------

One thing that is really handy is to be able to format a list like this:

TERM
  node title ...
  node title ...
  node title ...
TERM
  node title ...
  node title ...
  node title ...

A great example would be recipes:

Chinese
  Dou Miao -- Dou Miao is a...
  Beef and Broccoli -- Descriptions are good
  Szechuan Pork -- Dig your fork into...

Italian
  Lasagna -- Noodles and sauce
  Bolognese -- Meat meat and sauce
  Linguine alle Vongole -- Clams make this dish...

and so on.

Views can sort things this way, but it has no facility to add headers. Why?
Well, we had to stop somewhere. The good news is, this is actually not too 
difficult to accomplish! We set up our theme as a List View, given the fields
we want to put in. We could do this as a table view as well, but we'll have
to adjust our theme slightly. That is left as an exercise to the reader.

In order to designate what we want to group by, we'll give it a special label
so that we can identify it in code. I'll call this HEADER, which is actually
literal (unlike the _VIEWNAME that I always use).

function phptemplate_views_view_VIEWNAME($view, $type, $nodes) {

  if ($type == 'page')
    // Done before theming so theme can change it if it wants.
    drupal_set_title(_views_get_title($view));

  if ($view->header) {
    $header = check_markup($view->header, $view->header_format, false);
    $output = "<div class='view-header' id='view-header-$view->name'>$header</div>\n";
  }

  $header = '';
  $newheader = '';

  foreach ($nodes as $node) {
    $item = '';
    foreach ($view->field as $field) {
      // Look to see if the field is our header field.
      $data = _views_handle_field($fields, $field, $node);
      if ($field['label']) == 'HEADER') {
        if ($header != $data) {
          // if the contents of that field have changed, we change to a new header.
          $newheader = $data;
        }
      }
      else if ($field['label'])
        $item .= "<div class='view-label' id='view-label-$field[queryname]'>" . $field['label'] . "</div>";
      $item .= "<div class='view-field' id='view-data-$field[queryname]'>$data</div>";
    }

    if ($newheader) { // if set it means we got a new header, so we want to display the old one:
      if ($items) {
        $output .= theme('item_list', $items, $header);
      }

      // reset values for the next one.
      $items = array();
      $header = $newheader;
      $newheader = '';
    }
    $items[] = "<div class='view-item' id='view-item-$view->name'>$item</div>\n"; // l($node->title, "node/$node->nid");
  }

  // Do this again to dump the last list.
  if ($items)
    return theme('item_list', $items, $header);

  // you probably don't need this but it is left in for completeness.
  if ($type == 'block' && $view->block_more && count($nodes) >= $view->nodes_per_block) {
    $output .= theme('views_more', $view->name); 
  }
  return "<div class='view' id='view-$view->name'>$output</div>\n";
}

How to display a summary view as a simple list
----------------------------------------------

Sometimes the table is just not what you want there. No problem.

function phptemplate_views_summary_VIEWNAME($view, $type, $level, $nodes, $args) {
  if ($type == 'page') {
    $title = $view->argument[$level]['title'];
    if (!$title)
      $title = $view->title;
    drupal_set_title($title);
  }

  $base = views_get_summary_link_base($view->argument[$level]['type'], $view->name, $level, $args);
  foreach ($nodes as $node) {
    $items[] = views_get_summary_link($view->argument[$level]['type'], $node, $base);
  }
  if ($items)
    $output .= theme('item_list', $items);
  
  return "<div class='views' id='$view->name'>$output</div>";
}

How to display a summary view as a mock-menu
--------------------------------------------
On the surface, this looks like it's just a 'simple' list. But it turns out it
isn't quite so simple, because to do a menu properly, you need to modify
the link returned. Which means you need to actually know a little bit about
how views_get_summary_link functions. A little code exploration is required
to properly write this, but luckily, you're reading an example and that code
exploration was already done for you.

None of the theme_menu themes actually support sending something arbitrary
through, but only accept menu-ids. That's great, but we want more than that.

There is one BIG gotcha in this theme, and that's that you HAVE to customize
this just slightly for each argument type. The reason for this is that the
information retrieved from the database for a given argument type varies 
by name based on what kind of argument it is. Ok, it shouldn't, but I admit
I didn't think of that when writing it. Perhaps in a future version this
will be fixed.

In any case, you'll need to look in views_internal_arguments (or in the
module if you're using an argument supplied externally) and find the
handler for that argument. Go to the handler and look under case 'link':
That'll tell you what field is used to construct the link.

In the case below, we're doing it for the month argument, and we've determined
that the fieldname is 'name' from views_handler_arg_month(). Secondly, the
MONTH argument just displays the month, but I wanted it to do Month, Year.
Picky picky, I know. And I had to rip out code from theme_menu. 

function phptemplate_views_summary_VIEWNAME($view, $type, $level, $nodes) {
  $fieldname = 'name';

  if ($type == 'page')
    drupal_set_title(_views_get_title($view));

  // Get the page we're currently on.
  $path = drupal_get_normal_path($_GET['q']);

  $output = "<div class='menu'>\n<ul>\n";

  $base = views_get_summary_link_base($view->argument[$level]['type'], $view->name, $level, $args);
  foreach ($nodes as $node) {
    $tpath = "$base/$node->name";
    $class = ($path == $tpath ? 'leaf-active' : 'leaf');
      $list = "<li class='$class'>" . l(format_date($node->created, 'custom', 'F, Y'), $tpath) . "</li>\n" . $list;
  }
  $output .= "$list</ul></div>";
  
  return "<div class='views' id='$view->name'>$output</div>";
}


How to display a list view as a mock-menu
-----------------------------------------
This is very much like the previous example, so read the commentary there
about the gotchas.

The 'more' bit is left out because I didn't want it for this one, but 
it can always be pasted back in from earlier examples.

function phptemplate_views_view_VIEWNAME($view, $type, $nodes) {

  if ($type == 'page')
    drupal_set_title(_views_get_title($view));

  // Get the page we're currently on.
  $path = drupal_get_normal_path($_GET['q']);

  $output = "<div class='menu'>\n<ul>\n";

  foreach ($nodes as $node) {
    $tpath = "node/$node->nid";
    $class = ($path == $tpath) ? 'leaf-active' : 'leaf';
    $output .= "<li class='$class'>" . l($node->n_title, $tpath) . "</li>\n";
  }
  $output .= "</ul></div>";

  return "<div class='views' id='$view->name'>$output</div>";
}



How to display a summary view as a comma separated list (A, B, C)
-----------------------------------------------------------------
Sometimes the table is just not what you want there, but neither do you
want one line per page. With this version of the summary you may want it
embedded somewhere, because you may run into trouble with page-sizing. 
See the embedding example.

function phptemplate_views_summary_VIEWNAME($view, $type, $level, $nodes, $args) {
  if ($type == 'page') {
    $title = $view->argument[$level]['title'];
    if (!$title)
      $title = $view->title;
    drupal_set_title($title);
  }

  $base = views_get_summary_link_base($view->argument[$level]['type'], $view->name, $level, $args);
  foreach ($nodes as $node) {
    $item = views_get_summary_link($view->argument[$level]['type'], $node, $base);
  }
  $output = theme('links', ', ');

  return "<div class='views' id='$view->name'>$output</div>";
}

