Custom Theme

One of Drupal's primary powers is the seperation of the data processig from the presentation.  Lasqueti.ca takes full advantage of these abilities by defining a custom theme.  The "lasqueti theme" is documented here, along with some sub-pages that describe some of the custom theme programming.

The Lasqueti theme is a "hard-coded" version of the Denver theme.  Denver is a very flexible theme and specifies vitually everything with configurable parameters.  That is great for proto-typing, but slow for the production site.
The Denver theme was set-up to look "correct", and them copied to the Lasqueti theme.  The Lasqueti theme was then editted to "hard code" the various parmater values set in Denver.

Several additional changes were made to Lasqueti theme, for example:

All of this is documented in the sites/all/themes/lasqueti readme.

The Lasqueti theme could have a set of sub-themes, which simply change the banner for special parts of the site (e.g., like the marketplace) or provide style to hide images or other bandwidth intensive content (a low-bandwidth style).

Important - from the Denver Theme readme file:
"to prevent rendering errors in IE you must enable the CSS Aggregator in admin/settings/performance... Enable "aggregate and compress CSS files" to make your production drupal site run faster, and to prevent IE rendering errors."

Custom Code in PHPtemplate

The following functions were added to PHPtemplate in the lasqueti theme to obtain the required functionality.  Some of these should probably be made into a custom module.

Custom Node (page) titles using images

see: HowTo: Replace node title with a related image | drupal.org

<code>

// This function is used to substitue the node title with an image of the same name.
// Really good candidate to turn into a small module.
function _phptemplate_variables($hook, $vars = array()) {
  switch ($hook) {
    case 'page':
    // substitute node title with an image, if a suitable replacement can be found.
    // save original title text for use in head and breadcrumb.
    $vars['breadcrumb_title'] = $vars['title'];
    $vars['head_title'] = $vars['title'];
    // Substitute title only for nodes...
    if (arg(0) == 'node' && is_numeric(arg(1))) {
      $node = node_load(arg(1));  // (expensive, unless arg(1) is already loaded, which is should be at this point.)
      // ... of one of the listed types - add node types to process to the array.
      if (in_array($node->type, array('page', 'page_layout_b'))) {
        // convert title to suitable filename and derive both file system path and URL.
        $titleFile = clean_title($vars['title']) . '.gif';
        $titleImage = base_path() . "files/images/titles/" . $titleFile;
        $titleURL = "http://" . $_SERVER['SERVER_NAME'] . $titleImage;
        $titlePath = $_SERVER['DOCUMENT_ROOT'] . $titleImage;
        // $vars['title'] = $titleURL;   // DEBUG - see what's being produced

        // Determine if a suitable replacement image for title can be found, and make substitution.
        if ( file_exists($titlePath) ) {
          $newTitle = '<img alt=\'' . $vars['title'] .'\' src=\'' . $titleURL .'\' />';
          $vars['title'] = $newTitle;
        }
      }
    }
    break;
  }
 
  return $vars;
}

// Prepare some general title text for use as a file name.
// Remove special HTML characters, trim whitespace, convert to lower-case
// repace spaces with underscores.
function clean_title($string)
{
    $cleanString = htmlspecialchars_decode( strtolower(trim($string)), ENT_QUOTES );
    $cleanString = str_replace(array("& ", "'"), '', $cleanString);
    // $string = strtolower(preg_replace("/[^a-zA-Z0-9 ]/", "", $string));  // slower, but more general purpose.
    return str_replace(' ', '_', $cleanString );;

}
</code>

Custom Theming for Summary Views

<code>
 
// Create a mock-menu from a view's summary (usually for a block view)
// See Example: How to Display a Summary View as a Mock Menu http://drupal.org/node/42605
function _summary_menu($view, $level, $nodes) {
  foreach ($nodes as $node) {
    $list .= '<li class="leaf">' . views_get_summary_link($view->argument[$level]['type'], $node, $view->real_url) . "</li>\n";
  }

  if ($list) {
    return "<div class='menu'><ul>$list</ul></div>";
  }
}

// Photo albums are per-user, so agrument is user ID UID
function phptemplate_views_summary_photo_albums($view, $type, $level, $nodes, $args) {
  if ($type == 'block') {
    return _summary_menu($view, $level, $nodes);
  }
  else {
    drupal_add_css(drupal_get_path('theme', 'lasqueti') .'/css/image_gallery.css', 'theme');
    // Need generic way to get image file info? - must use name of CCK field.
    // Replace this with the name of your image field.
    // Uncomment the print_r below to figure out what it is.
    $cck_fid_field = 'node_data_field_image_field_image_fid';
    $content .= '<ul class="galleries">';
    $title = "'s Personal Photo Album";
    $baseUrl = $view->url;
    // Build a small, single node teaser for each query in the summary and
    //   use it as a link to the actual view page with that argument.
    foreach ($nodes as $query) { 
       // Get the argument for this view from the query.
       $uid = $query->uid;     // argument is user id;
       $user = $query->name;   // query also has user's name.
       // Build a 1 item query to grab the first node in this view
       $result = views_build_view('items', $view, array($uid), false, 1);

       // Grab the image from that node to display in our summary.
       // The file id comes from the node info. we got back from views.
       // print_r($result['items'][0]);
       $fid = $result['items'][0]->$cck_fid_field;
       $modDate = $result['items'][0]->node_changed;

       // Query the database to get the file object
       $file = _imagefield_file_load($fid);
       $imgTag = theme('imagecache', 'Thumbnail', $file['filepath'], 'Latest image from gallery', $user . $title);

       $url= $baseUrl .'/'. $uid;
       $linkText = $imgTag .' '. $user . $title;
       $content .= _theme_gallery_summary($imgTag, $url, $user.$title, $query->num_nodes, $modDate, NULL);
     }
    $content .= "</ul>\n";

    return $content;
  }
}

// Produce the themed output for the image gallery summary.
// This code was ripped from the image gallery module.
function _theme_gallery_summary($imgTag, $url, $title, $count, $modDate, $description=NULL)
{
   $content = "";
   $content .= '<li class="clear-block">';
   if ($count>0) {
     $content .= l($imgTag, $url, array(), NULL, NULL, FALSE, TRUE);
   }
   $content .= "<h3>". l($title, $url) ."</h3>\n";
   if ($description) {
      $content .= '<div class="description">'. check_markup($description) ."</div>\n";
   }
   $content .= '<p class="count">'. format_plural($count, 'There is 1 photo in this gallery', 'There are @count photos in this gallery') ."</p>\n";
   if ($modDate) {
     $content .= '<p class="last">'. t('Last updated: %date', array('%date' => format_date($modDate))) ."</p>\n";
   }
   $content .= "</li>\n";
   return $content;
}

// Photo galleries are per-term, so argument is taxonomy term.
function phptemplate_views_summary_photo_gallery($view, $type, $level, $nodes, $args) {
  if ($type == 'block') {
    return _summary_menu($view, $level, $nodes);
  }
  else {
    drupal_add_css(drupal_get_path('theme', 'lasqueti') .'/css/image_gallery.css', 'theme');
    // Need generic way to get image file info? - must use name of CCK field.
    // Replace this with the name of your image field.
    // Uncomment the print_r below to figure out what it is.
    $cck_fid_field = 'node_data_field_image_field_image_fid';
    $content .= '<ul class="galleries">';
    $title = " Photo Gallery";
    $baseUrl = $view->url;
   
    // Build a small, single node teaser for each query in the summary and
    //   use it as a link to the actual view page with that argument.
    foreach ($nodes as $query) { 

       // Get the gallery description from the term's vocab.
       $term = $query->letter;  // argument for taxonomy terms;
       $tid = $query->tid;      // term IS the argument!
       $termObj = taxonomy_get_term($tid);
       $description = $termObj->description;
       
       // Build a 1 item query to grab the first node in this view
       $result = views_build_view('items', $view, array($term), false, 1);

       // Grab the image from that node to display in our summary.
       // The file id comes from the node info. we got back from views.
       // print_r($result['items'][0]);
       $fid = $result['items'][0]->$cck_fid_field;
       $modDate = $result['items'][0]->node_changed;

       $file = _imagefield_file_load($fid);
       $imgTag = theme('imagecache', 'Thumbnail', $file['filepath'], 'Latest image from gallery', $term . $title);

       $url= $baseUrl .'/'. $term;
       $linkText = $imgTag .' '. $term . $title;
       $content .= _theme_gallery_summary($imgTag, $url, $term.$title, $query->num_nodes, $modDate, $description);
     }
     $content .= "</ul>\n";

     return $content;
  }
}

Theme the node add / edit forms for custom CCK types

    This simply addes a collapsible fieldset for the CCK fields in the "Market Page" type, and hides the Categories fieldset from non-admins, to try to improve useability. See http://drupal.org/node/101092#comment-748013 for details.

function phptemplate_page_layout_b_node_form($form) {
  // Collapse the taxonomoy fieldset by default (as this rarely changes).
  $form['taxonomy']['#collapsed'] = true;
 
  // enforce crude access rights on the taxonomy terms.
  global $user;
  if (!($user->uid==1 || in_array('contributor',$user->roles)) ) {
    unset($form['taxonomy']);
  }
  // These are the fields that should be in the fieldset
  $fields = array('field_teaser_image', 'field_bio_image', 'field_bio');
 
  // Add fieldsets to the page_layout_b input form & render it.
    _add_fieldset($form, 'Images', $fields);
    return drupal_render($form);
}

/**
 * Add the given fields to a new fieldset on the form.
 * @form - the form to add the fieldset to
 * @name - a string name of the fieldset
 * @fieldsInSet - field ids (names) for the fields in
 *     the form that should go into the fieldset.
 */

function _add_fieldset(&$form, $name, $fieldsInSet) {
  // Add a fieldset to the form to hold related fields.
  $form[$name] = array (
     '#type'        => 'fieldset',
     '#title'       => $name,
     '#collapsible' => 1,
     '#collapsed'   => 0,
     '#weight'      => -2,
     '#tree'        => 1,
  );
 
  // Move all the related fields into the new fieldset.
  foreach ($fieldsInSet as $field) {
    if ($form[$field]) {
      $form[$name][$field] = $form[$field];
      $form[$name][$field]['#parents'] = array ($name, $field);
      unset($form[$field]);
    }
  }
}

Custom theme the book navigation links.

    When a node's page is viewed in a tab, we don't want to show any book navigation.

function phptemplate_book_navigation($node) {
  if ($node->inTab)
    return NULL;
  else
    return theme_book_navigation($node);
}
 

Custom display of Views exposed filter

  Exposed filters allow the user to have control over what nodes are viewed by selecting from a widget.  The default layout is to have the widget displayed above the view, in the page - yuck - very ugly and poor usability.   This  "snippet" hides the default exposed filter for a view with the name "test" - it can be copied for any View where you want to do this.  Then a custom block is created to display the exposed filter widget off to the side, or "rolled-up"!  Much nicer.
See http://www.angrydonuts.com/displaying_views_exposed_filters

function phptemplate_views_display_filters_test() {
    // Do absolutely nothing.
  }
 

Custom Forms

Anywhere the user can enter data is a form.

Mostly, these forms are generated by Drupal and the sequence the fields are presented is controlled using the field weights (manage fields under Content Types).  However, there were a few cases where input forms were customised to improve their usability (mostly to make them less daunting to the community).

  1. Page node input forms.  These forms display when the a new Page node is created or when a Page node is editted. Customized node forms include page_layout_b (Market Page).  These are long, fairly complicated forms, so I grouped some of the fields into fieldsets to make them easier to manage.  Code includes:
    • phptemplate_node_form($form) - selects forms to be modified and applies custom template
    • also provides a crude access control mechanism to hide the Taxonomy selectors, which generally don't need to change for these types of nodes once they have been set (by the admin on page creation).
    • See http://drupal.org/node/101092#comment-748013 for details
  2.  

 

Custom Templates

Any page, node, or block can have a custom template that defines its look.  This is a very easy way to subtly alter the look of different parts of the site, or to customize the display of different types of data.

page.tpl.php

Heavily modified the Denver Theme version to remove almost all conditional theming and unused divs.  Made a few other customizations to:

node.tpl.php

Basically, this is straight from the Denver theme, but with:

  • "teaser" class added to the "node" div to allow custom styles for teasers (used to float image right on node page, but left on teaser view).

page_footer.inc

Defines the footer portion of the page that includes the "Our Community" and "MarketPlace" random image blocks.

node-page_layout_b.tpl.php

Defines a custom layout for the "Market Page".  Basically, this just adds the code to pull in the "mini-gallery" at the bottom of the page.

commments.tpl.php

Added this to customize the look and feel of comments. 
See Theming Drupal Comments, Exemplifying with Garland | All Drupal Themes

Modulettes

Some things just can't be developed as a full-fledged module...
Sometimes a hard-coded argument is required (e.g., specify vocabulary for a view), sometimes the code just needs further development to create a nice, general-purpose module.  In any case, these small "modulettes" are less than a full-fledged module, but more than simply a snippet.  They all perform some custom theming, often for a summary view of some sort. 

Directory View

Summary

This module creates a view of nodes by-category, to form a simple, 2-column directory.

Overview

Requires:

  • a taxonomy to list nodes by category - I use the primary "Category" vocabulary here;
  • a View to select the nodes to be listed in the directory and create a nice layout for them;  I use the 'market_directory' view to select Marketplace nodes, and format them in a "Table" layout.

Options:

  • Any number of columns can be created;  I am using a 2-column display.
  • The "depth" of traversal can be set as the views argument option;  I use a depth of 2 here.
  • A complete listing a all terms is possible; I only list terms associated with some node data.
  • By default, each tree for top-level terms is put into a collapsed fieldset using the term as the legend.  This creates a very tidy, minimalist view of the directory, making it easier to navigate.

Source Code

All code snippets, modules, and modulettes on this site are covered by GPL - see license.txt

The source code is attached below...

Full instructions for installing and using this module are included as comments in the source file.

AttachmentSize
directory_view.zip15.28 KB

Image Gallery Summary View

Summary

This module themes a views summary for a set of image galleries.  The image galleries are defined with a view using an argument (usually Term or User).  The summary view shows a thumbnail of the latest image from each gallery, the page title, last modified date, and number of images in the gallery.  This is nearly identical to the gallery summary provided by the Image module's Image Gallery contrib module.  This can be used to theme per-user gallery summaries or per-term gallery summaries.   I can't think of (and haven't tested) other possibilities, but it should work with any argument type and any node type that defines at least one image where you want a nice looking summary.

  • I just really liked the look of the Image Gallery module's summary page, but I wanted to use CCK imagefield rather than the Image module.  There seem to be a lot of people out there looking for this, but there just were no nice looking summary views available. 
  • All of the theming code was ripped directly from image_gallery.module : http://drupal.org/project/image
  • The module is used, for example,  to create the summary view at: http://lasqueti.ca/photos/gallery

Overview

Requires:

  • a view with (1) a CCK imagefield defined as one of the fields; and (2) a view argument (like Term or User)
  • an imagecache preset named "Thumbnail" (this can be configured in the script)
  • a style-sheet for styling the gallery summary view - I've included image_gallery.css, ripped directly from the Image module.

Options:

  • combine this with the Views Summary Mock Menu modulette for a complete image gallery browsing experience!

Source Code

All code snippets, modules, and modulettes on this site are covered by GPL - see license.txt

The source code is attached below...

Full instructions for installing and using this module are included as comments in the source file.

AttachmentSize
summary_view_image_gallery.zip15.01 KB

Image Titles

Summary

This module "automatically" replaces text titles with an "associated:" image, if a suitable replacement can be found.  It simply converts the title into a filename and looks for that file - if it finds it, it formats the image tag to replace the title.

Overview

Requires:

  • a collection of GIF image titles located in files/images/titles (configurable in script)
  • a call from _phptemplate_variables(), or some other appropriate place, to substitute the title.

Source Code

All code snippets, modules, and modulettes on this site are covered by GPL - see license.txt

The source code is attached below...

Full instructions for installing and using this module are included as comments in the source file.

AttachmentSize
replace_title.zip13.35 KB

View Summary "Mock Menu"

Summary

This very tiny module creates a "mock menu" from a views summary - a menu item is created for each query, or potential argument value, in the views summary.  This is handy for providing a menu that links to the various pages provided when a view has an argument.

Source Code

All code snippets, modules, and modulettes on this site are covered by GPL - see license.txt

The source code is attached below...

Full instructions for installing and using this module are included as comments in the source file.

AttachmentSize
summary_view_mock_menu.zip13.03 KB

Vocabulary "Mock Menu"

Summary

This module creates a "mock menu" from a vocabulary, with links to the terms at a given base-path.  This is very handy for providing a menu that links to a view with a Taxonomy:Term ID argument.

Overview

Requires:

  • a vocabulary to create the menu items from;  I use the primary "Category" vocabulary here;
  • a base-path for the menu links - the term ID will be added to this base-path; my base-path is "marketplace/browse/listings"

Options:

  • most useful when combined with a View using a Taxonomy:Term ID argument;  I use the 'market_listing' view to generate this menu as a "summary view" in a block displayed at the base-path.
  • The "depth" of traversal can be set to limit the menu depth;  I use a depth of 2 here.

Source Code

All code snippets, modules, and modulettes on this site are covered by GPL - see license.txt

The source code is attached below...

Full instructions for installing and using this module are included as comments in the source file.

AttachmentSize
category_mock_menu.zip13.95 KB