Aug 10 2015
Aug 10

If you are a Drupal developer who has dabbled in theming older versions of Drupal (5, 6, 7) you understand why frustration is the trusty companion of any Drupal themer. Luckily, though, Drupal 8 promises so many improvements that even the Angry Themer is happy for a change. It is only natural we jump in and start looking at what these improvement are.

Drupal 8 logo

In this article, we will look at some of the more important changes to theming in Drupal 8. Although we will keep things simple and start from the basics, I do assume you have at least a bit of experience with theming in Drupal 7. And since theming is a big subject and this is just an introduction, you’ll find all sorts of links to more information that can help you out.

Starting up

As with custom modules, a new theme always starts with a folder and the obligatory .info.yml file inside (as opposed to the old .info file). These go in the root themes folder of the Drupal codebase (as opposed to the old sites/all/themes directory) and they should both have the same name (e.g. my_theme.info.yml within the my_theme folder).

Inside the .info.yml file we provide a few required keys:

name: Theme name
description: Theme description
type: theme
core: 8.x

All the rest are optional and come as needed. With this in place, you can already navigate to admin/appearance and enable the new theme or set it as default. You’ll notice that from the good ‘ol Bartik you now have a really naked theme in place. But the big difference from Drupal 7 is that you can easily start theming every aspect of your website.

A major improvement is that we now have an intermediary core theme called Classy which bridges the gap between the data output by the Drupal backend (usually the system module) and the actual themes. So what all the other available core themes do (and what you can as well) is use Classy as their base theme and override its templates:

base theme: classy

Alternatively, you can also not do this but copy all of its template files into your theme and you’ll end up at the same place. But you probably won’t need all those files (some might not need changing) so in my opinion it’s just better to use Classy as a base theme and just override what you need.

Although I won’t go into details, region definition is also quite an important aspect of a theme’s info.yml file:

regions:
  header: 'Header'
  content: 'Content'
  footer: 'Footer'

With this in place, inside your page.html.twig template file you can print these regions out like so:

{{ page.header }}

This is the Twig templating syntax you should start getting familiar with if you haven’t already.

Twig

By now I think everybody knows that Twig is the templating language used in Drupal 8. I won’t go into it too much because there are many resources out there with plenty of information about how Twig syntax can make themers forget all about PHPTemplate in no time.

But one important thing to keep in mind is that there are no more theme functions in Drupal 8. This means that all themable output is run through an html.twig file. We can still use hook_theme() to define reusable theme implementations, but they will now all use Twig files. And the cool thing is that these Twig templates are extensible. This means they can define only the necessary bits related to them and inherit the rest from their parent. Check out the Twig extends documentation for more information on what I mean.

Templates

I mentioned before how in Drupal 8 we are in full control over the markup of our site due to everything being neatly organised in template files within the Classy theme. So one of the next steps in building your theme would be overriding the html.html.twig and/or page.html.twig files to provide markup for your pages. Doing so, you get to play with semantic HTML5 markup because that is what Drupal 8 outputs by default.

All of these template files have documentation at the top with information about what variables you have available. Additionally, you can make use of various Twig filters and functions to manipulate this data straight from the template files. It is in fact recommended, for instance, to build translatable strings or urls straight in there rather than the preprocessor in order to maybe avoid unnecessary function calls (if these don’t actually end up being printed).

And speaking of preprocess functions, they do still exist. However, there is no longer a template.php file to put them in but rather a .theme PHP file (my_theme.theme) inside the root theme folder.

An interesting note about preprocessors is that in Drupal 8 we have to try to always prepare render arrays for the template file variables rather than final markup as we often do in Drupal 7. Of course, this is only if the data needs to be rendered and is not already just a simple string or something primitive like that. The point is to no longer call drupal_render() within our preprocessor functions but let the Twig magic handle that for us.

Debugging

There are many improvements made also to debugging of theme information. By turning on Twig debugging (debug: true) inside the sites/defaults/services.yml file, you get a bunch of helpful HTML comments printed in the page source.

Theme debugging in Drupal 8

These allow you to see which template file is responsible for a particular piece of markup, where it is located and what theme suggestions you can use to override it. No more spending hours trying to figure out what to override. This is a great win!

Additionally, Twig comes with the dump() function which allows you to print out a particular variable to the page from Twig. However, if that is not enough for you, the Devel module comes with the kint() function that provides a better (traversable) variable inspection tool. This is the new Krumo.

Assets and libraries

The last thing we are going to cover here is the topic of assets (CSS and JavaScript files). A notable change is that Drupal 8 ships with jQuery 2.x alongside other modern frontend libraries like Modernizr, Backbone.js and Underscore.js. And if you ever had to work with jQuery in Drupal 7 you’ll understand why this is no small gain. Plus this means that IE8 and lower are no longer supported!

Additionally, Drupal 8 has adopted a SMACSS based CSS file organization and we have some [good architecture and best practices] (https://www.drupal.org/node/1887918#best-practices) in place as well. No more excuses for messy CSS in our theme!

One thing that trips people up in the beginning is that, for performance reasons, assets are no longer added indiscriminately to every page. So if that Ajax functionality you’re trying out doesn’t work, make sure Drupal has loaded the necessary scripts for it. You can do so by declaring them as dependencies to your own.

What’s great is that we now have a unified way of doing all of this across the entire development spectrum. We use libraries that contain both javascript and css files, which can have dependencies on other assets and which get #attached to render arrays. If you want them loaded on all pages, you can also add them to your theme’s info.yml file or implement hook_page_attachments() and add them like that. However, it is recommended to always attach libraries to render arrays to make sure your assets don’t get loaded unless they are really needed and that they are cached properly with the data they serve.

Conclusion

In this article we’ve looked at some of the more prominent changes to theming in Drupal 8. We’ve done so by taking a look at the starting point from which we create new themes and moved through some of the main topics related to this process. By no means, however, has this been a complete rundown of all the changes. I recommend keeping up to date with the documentation on Drupal.org (which is also constantly updating) and jump in the code yourself. It should be fun!

Aug 18 2014
Aug 18

In this article, we’ll discuss how you can leverage various Drupal API functions to achieve more fine grained theming. We’ll cover template preprocessing and alter hooks using path patterns, types and args(). We’ll use the arg() function which returns parts of a current Drupal URL path and some pattern matching for instances when you want to match a pattern in a URL. We’ll also take a look at creating a variable for an array of content types.

Template preprocessing is a means to define variables for use within your page. For example, you can define a body class variable. In turn you can use the resulting class in your Sass or CSS. An alter or build hook is something that’s run before the page renders so you can do things such as adding JS or CSS to specific pages or content types.

I’ll explain and demonstrate these hooks and how you can use them to:

  • Add a <body> class to a specific page for better theming
  • Add Javascript or CSS to specific pages and paths
  • Use wildcard path arguments
  • URL pattern matching using preg_match
  • Create an array of content types to use as a variable in your argument
  • Using path arguments as parameters

The functions we discuss here will be added to your theme’s template.php file. Although you can also use these functions in a custom module, you’ll need to specify that the functions are not for admin pages unless that’s your intent, and I’ll cover how to do that.

Getting Started

When using preprocess or alter functions within your theme, you’ll want to be sure template.php exists but if not, you can go ahead and create this file in the root of your theme. So if my theme name is foobar, the path to template.php will be:

/sites/all/themes/foobar/template.php

API functions are prefaced by the machine name of your theme. For example, if you are using hook_page_alter and your theme name is foobar, we’d write it as function foobar_page_alter(). (The machine name is simply the theme’s folder name.)

Custom Body Classes Using a Content Types Array

A body class is a class that’s added to your HTML <body> tag. For example, on a blog page, you might see something like this:

<body class="page-node page-node-blog">

You can leverage that class in your Sass or CSS to fine tune your theming by doing something like this just for blog pages on your site:

.page-node-blog h1 {
// custom Sass here
}

Out of the box, Drupal 7 comes with some pretty good body classes and usually these are fine for most use cases. In addition, Drupal contribution or Contrib themes such as Zen add enhanced and expanded classes.

In our case, we want to add a class to some pages which share some common attributes but may not necessarily derive from the same content type. Let’s say we have two content types that we want to add a specific class to in order to theme those alike but perhaps different from other pages on our website. We can build an array of Drupal content types we want to target and then use that array to add the class. Once we’ve defined the array, we just check to ensure that a given node exists and then pass the array in.

<?php

/**
 * Implements template_preprocess_html().
 *
 * Define custom classes for theming.
 */
function foobar_preprocess_html(&$vars) {

  // Build a node types array from our targeted content types.
  $foo_types = array(
    'landing_page',
    'our_services',
  );

    // Define the node.
    $node = menu_get_object();
  
  // Use the array to add a class to those content types.
  if (!empty($node) && in_array($node->type, $foo_types)) {
    $vars['classes_array'][] = 'page-style-foobar';
    }
  }

This function preprocesses variables for anything that would typically be before the ending HTML </head> tag which is in Drupal’s core template, html.tpl.php. We add the body class using $vars['classes_array']. This variable gets rendered with <?php print $classes; ?> in the <body> tag. In our case, this class will only render in landing_page and our_services content types. Now we can use .page-style-foobar in our Sass or CSS to style these pages.

URL Pattern Matching

You can also use URL pattern matching for adding useful custom classes. Let’s say we have an “Our Services” landing page and then some sub-pages under that path. The URL architecture might look like this:

example.com/our-services
- example.com/our-services/subpage-1
- example.com/our-services/subpage-2

We’ll add a custom body class to those pages using preg_match, regex, PHP matches and Drupal’s request_uri function. Once again, we’d put this in a foobar_preprocess_html as above.

 <?php
function foobar_preprocess_html(&$vars) {

  // Define the URL path.
  $path = request_uri();

  // Add body classes to various pages for better theming.
  if (preg_match('|^/our-services((?:/[a-zA-Z0-9_\-]*)*)?|', $path, $matches)) {
    $vars['classes_array'][] = 'page-services';
  }
}

Now you can use .page-services .some-common-element for theming these “our-services” pages. Obviously this method has pitfalls if your URL structure changes so it may only fit some use cases.

Path Arguments

Another clever way of custom theming specific parts of your site is to partition those off using arg(). Drupal tends to get script and CSS heavy so ideally if you’re adding extra CSS or JS and you don’t need it for every page (which is normally done using your theme’s .info file), you can use path arg() to add these only to pages where they’re needed. For example, if I want to add a custom script to just the Drupal registration page, I can create a path arg() if statement and then add the script within that. The URL path we’ll focus on here is /user/register and you’ll see how the arg() breaks down the URL structure.

We’ll be using hook_page_alter here which can apply alterations to a page before it’s rendered. First we define the theme path and use Drupal’s attached function.

<?php

/**
 * Implements hook_page_alter().
 *
 * Add custom functions such as adding js or css.
 */
function foobar_page_alter(&$page, $form) {

  // Define the module path for use below.
  $theme_path = drupal_get_path('theme', 'foobar');

  if (arg(0) == "user" && arg(1) == "register") {
     $foobar_js = array(
      '#attached' => array(
        'js' => array(
          $theme_path . '/js/custom.js' => array(
            'group' => JS_THEME,
          ),
        ),
      ),
    );
    drupal_render($foobar_js);
  
  }
}

In this function, note that we’ve substituted our theme name for hook so hook_page_alter becomes foobar_page_alter. The arg() number signifies the position in the URL path. Zero is first, one is second and so on. You can get pretty creative with these adding more parameters. Let’s say you wanted to add JS to just the user page but no paths underneath it. You could add a NULL arg() after the initial arg().

if (arg(0) == "user" && arg(1) == NULL) {
// code here
}

In the examples above, we’ve implemented various functions in our theme’s template.php file. You can also use these in a custom module as well and in that case you’d just preface your module name in the function rather than the theme name. When theming from a module, you’ll probably want to exclude admin paths since a module can target anywhere in your site. You can do that excluding admin paths.

if (!path_is_admin(current_path())) {
// code here
}

Conclusion

As you can see, leveraging the Drupal API Toolbox for theming extends your reach as a developer. The examples above are not definitive but they do give you a feel for what’s possible. As a Drupal Themer, using these has helped me expand my bag of tricks when theming and building a site. Comments? Feedback? Leave them below!

Resources

Feb 25 2013
Feb 25

In the Drupal community, we always recommend using the Drupal API, and best practices for development, management and deployment. This is for many reasons, including modularity, security and maintainability.

But it is also for performance that you need to stick to these guidelines, refined for many years by so many in the community.

By serving many clients over many years and specifically doing Drupal Performance Assessments, we have seen many cases where these guidelines are not followed, causing site slowdowns and outages.

Here are some examples of how not to do things.

Logic in the theme layer

We often find developers who are proficient in PHP, but new to Drupal misuse its API in many ways.

In extreme cases, they don't know they should write modules to house the application logic and doing data access, and leave only presentation to be done in the theme layer.

We saw a large site where all the application logic was in the theme layer, often in .tpl.php files. The logic even ended with an exit() statement!

This caused Drupal page caching mechanism to be bypassed, resulting in all page accesses from crawlers and anonymous users to be very heavy on the servers, and complicating the infrastructure by over-engineering it to compensate for such a development mistake.

Using PHP in content (nodes, blocks and views)

Another common approach that most developers start using as soon as they discover it, is placing PHP code inside nodes, blocks or views.

Although this is a quick and dirty approach, the initial time savings cause lots of grief down the road through the life cycle of the site. We wrote an article specifically about that, which you will find a link to below.

Heavy queries in the theme layer, when rendering views

In some cases, the logic for rendering individual nodes within a view is complex, and involves code in the view*.tpl.php file that has SQL queries, or calls to heavy functions, such as node_load() and user_load().

We wrote an article on this which you can find the link to below.

Conclusion

Following Drupal's best practices and community guidelines is always beneficial. Performance is just one of the benefits that you gain by following them.

Further reading

Aug 29 2012
Aug 29

Zentropy started off as a Basic fork back in the Drupal 6 days and that version lives to this day in GitHub. It was officially published a year and a half ago and was one of the first Drupal 7 base themes to support HTML5. It has since gained a small but loyal following!

Did you know the 965 theme started off as a Zentropy fork? And that the awesome Sasson theme is a fork of 965? Don't you love open source?

What I really personally loved about Zentropy was that it was tailored exactly to the way I worked and created markup that was very easy to theme.

In the past few months I've been doing less and less theming and, when I've had to, used Omega, which is pretty amazing by the way.

Maintaining a theme is a lot of hard work and is a beast of its own and, unfortunately, not a beast I'm particularly interested in slaying for the forseeable future. So, in the best interests of the community, if anyone likes this theme and would like to take over maintaining it please get in touch!

Aug 20 2012
Aug 20

Posted Aug 20, 2012 // 0 comments

Last month we released our first Beta of OpenPublish on Drupal 7. Included in the release is a brand new demo theme called Gazette. Built as a sub-theme of Frame and based on the Omega base theme, Gazette is a fully responsive and a mobile friendly theme. It was originally designed by Samantha Warren and then adapted for responsive design by Dave Ruse. We were excited to add it to OpenPublish in the Beta release as a demo theme to show just what OpenPublish can do.

Our goal with this theme was to demonstrate that OpenPublish can handle a large amount of dynamic content and mobile-friendly, while still looking attractive and appealing to the eye. This theme features the use of the League Gothic open source webfont.

Gazette homepage

Gazette homepage on the iPhone

As part of the development of Gazette, we incorporated the PhotoSwipe javascript library into core OpenPublish. The Photo Gallery content type now supports swipe events on touch enabled devices. This was a feature that we felt was crucial to highlighting the mobile-friendly aspect of OpenPublish and prepares site administrators for supporting mobile in the future. The touch photo gallery functionality was actually de-coupled from Gazette, and built as part of the openpublish_media feature so all themes will have this functionality by default.

Gazette’s code can serve as a solid demonstration for themers who are just beginning with Omega or OpenPublish. Frame is a base theme for OpenPublish developed by Jake Strawn. It is also a sub-theme of Omega so it inherits all of Omegas capabilities, while adding basic theme support for all of OpenPublish’s functionality. We deliberately chose to build Gazette as a sub-theme of Frame to demonstrate how this is done, and to help define some best practices.

David Coffey’s user interface development skills form the bridge between Phase2’s design and development teams. His specializations in CSS, Javascript, and Drupal theming, plus his focus on Drupal as a platform, make David an ...

Sep 13 2011
Sep 13

Background Reading

The purpose of this post isn't to explain the concept of responsive design. There are many great resources out there that already do this, most notably Ethan Marcotte's article on A List Apart. Or better yet, read the book; it's a quick read and it's well worth it.

Nor is it my aim to debate the merits of responsive design. I'm already convinced that this approach is beneficial, and, again, there are so many voices in the community arguing this point that I don't feel the need to chime in.

Instead, this post will discuss Terrain, a specific Hexagon-based implementation of responsive design in Drupal.

The Concept

Both in principle and in practice, I don't like doing the same thing twice. Why add this snippet of code to every new theme I create?

img, iframe, video, embed { max-width: 100%; }

On one hand, every design is unique, and that is especially true when designing for multiple viewports and browser capabilities. Because of this, "some recommend against using a base theme at all". On the other hand, there is a set of design elements that can be generalized out to most responsive designs. In Terrain, these elements are:

  • Larger links for smaller viewports, making links easier to click on smaller touch devices
  • Max-width settings on images and other media
  • A Fluid grid
  • Single column layout on smaller viewports
  • Larger base font on larger viewports
  • Initial-scale settings to fix automatic resizing

I've found that starting out with these basic elements saves time when creating a new responsive theme. And with a starter theme in place for Terrain, it's easy to tailor the theme to your specific design.

This theme is also meant to give users a quick start to responsive design. Fire it up, without changing any configuration settings, and you have a simple, usable, 100% responsive theme.

Explaining Our Decisions

Why doesn't Terrain "respond" in Internet Explorer?

Simply put, we made a deliberate decision not to implement IE support, despite the availability of javascript libraries such as adapt.js and respond.js that were created for this purpose.

To borrow a quote from Jeffrey Zeldman's "To Hell With Bad Browsers":

For years, we’ve been taught to be good little web designers, building sites that work in browsers that don’t. Each site we build the old-fashioned way becomes one more dung heap of bad code, one more web destination that will eventually stop working as browsers and standards evolve.

The longer we do it, the more doomed sites proliferate. Thousands of new sites premiere every day. Most of them are built to support bad browsers intead of standards. It’s an epidemic. Enough already. We finally have good browsers. Let’s use them.

Even though this article was published in 2001, it still accurately summarizes my view on non-standards-compliant browsers. This doesn't mean Terrain will be unusable or ugly on Internet Explorer 8 and below, it just means that IE8- won't have the benefit of being able to utilize some of the responsive features, i.e. progressive enhancement for more capable browsers.

Why did you choose a fluid grid / responsive approach (as opposed to adaptive)?

From an ideological perspective, I wanted my designs to be able to truly respond to their environments, as opposed to creating 3-5 similar designs to "fit" different classes of device. This approach has several real-world advantages, from future-proofing against new sizes of tablets and other devices, to maximizing the use of (very) limited screen space on smartphones.

Of course there are also downsides with this method, like how much control you might have to concede over your pixel-perfect design. So if having that kind of control over your design is important to you or your project, Terrain might not be a good fit.

Why is Terrain based on Hexagon?

If you haven't heard of Hexagon, I highly suggest you go download it and try it out. Hexagon is a powerful base theme by the brilliant Joon Park (dvessel), with features like:

  • Built-in plugin system, with available plugins for Accessible Tabs and Accessible Accordions regions, region grouping, 960.gs, module compatibility, smarter styles, and more
  • Granular separation of files, such as variable preprocess functions
  • Better file caching and overrides

Building on this framework means Terrain can utilize all of Hexagon's features, while itself staying simple and lean.

Why does Drupal need another responsive starter theme?

Like most projects that appear to duplicate functionality, Terrain uses a slightly different approach. None of the responsive starter themes we tried quite fit our needs, so we rolled our own solution. As I mentioned above, we built Terrain to be extremely lean, while still leveraging the functionality of Hexagon.

Future Improvements

The first item on my Terrain to-do list is a 7.x version. A close second is a set of usability improvements, including better documentation, a quickstart guide (for both Terrain and Hexagon), and more helpful comments. Code improvements, such as more grid options and HTML5 support, extensible functionality, such as a responsive slideshow feature, and responsive image support are also coming soon.

Terrain is currently available for download as a 6.x-1.0-alpha1 release on drupal.org.

Aug 16 2011
Aug 16

Like many folks who build web sites, we're seeing increased traffic from people with mobile devices.

In addition to building web sites, we're also geeks, and based on our experience working and playing on the web using mobile devices, we know that people using handhelds want the same information as people browsing the web using a laptop, netbook, or a desktop. Believe it or not, our interests as end users do not shrink or grow in proportion to our screen resolution. As a result, we favor the principles of responsive design as a means to meet the needs of mobile users.

The terrain mole!

We've been using Hexagon as our base theme for the last few years. Hex is insanely flexible, designed to work smoothly with Context, designed to eliminate the pain of building subthemes, and has an architecture that supports functional plugins within the theme. Currently, Hex ships with plugin support for accordion regions, vertical tabs, and horizontal tabs. If you want to get a sense of how Hex works with subthemes, grab a copy of VoiceBox and look at how the Dispatch theme interacts with contexts.

But, because Hex is designed to be extensible (did I mention that Hex is insanely flexible?) it can support other types of plugins.

For example, a Hex subtheme can use a plugin that supports the 960 grid system with a custom fluid width grid. Along with some other modifications, which we'll discuss in another post, this is the basis for our responsive hex starter theme. A development release of Terrain is now available for download on drupal.org. Get it while it's hot!

You can also access this video directly on Vimeo: http://vimeo.com/27829604

Because we believe that one's own dog food is the tastiest, http://funnymonkey.com is now running on a Terrain-based theme.

Jul 08 2011
Jul 08

If you are a Drupal module developer, I have a request for you. A plea for you to help make my life easier, and likely the lives of many other site builders.

I'm currently building a website that uses a theme override to display a prettily formatted username. Drupal provides a way to do this via the theme_username() API call, so in all places where Drupal core displays a user's name, my pretty string now shows.

However, when I started adding contrib modules for extra functionality, I found that quite a few of these don't use the theme call to display a user's name. Instead, they simply print out the value of the name field in the user object. If I want to change that as a site builder, my only choice is to hack up the module so it displays what I need.

For example, a Drupal 6 module might set a page title on a tab somewhere in the user account like this:

drupal_set_title(t('Widgets for !username', array('!username' => $account->name)));

Whilst what it should be doing is passing the name through the theme layer first, like this:

drupal_set_title(t('Widgets for @username', array('@username' => strip_tags(theme('username', $account)))));

And the same goes for calls to drupal_set_message() for status or error notifications, or anywhere else a user's name might be displayed.

This way the site builder is in charge of how users are displayed and won't need to hack up any contrib modules. Though that's not hard, it makes updating them unnecessarily painful, as any changes would need to be re-applied.

So my plea is, if you're a module developer and your module displays $account->name somewhere, please change your module to use theme('username') instead.

Conversely, if you're using a contrib module and find that it prints $account->name when it really should use theme('username'), please open an issue and attach a patch if you can. I will :-)

Feb 10 2011
Feb 10

One question I'm often asked by many other diocesan web development teams/individuals is how we put together our online Mass Time search (also used for searching adoration and reconciliation times). We also get questions about how we do our online mapping—but I've already covered that (see: Beautiful, Easy Maps in Drupal using Views and Mapstraction).

Mass Times Search Interface
The Archdiocesan Mass Times search interface (click to enlarge)

We already have a database provided by the Archdiocesan IT department (they maintain it with the help of our diocesan Parish Support staff, and parish secretaries who can update their own schedules and information), so we needed to do the following on the web:

  • Import all the Sacrament time information and attach it to a parish node (so times/days could be affiliated with parishes).
  • Display the time information on parish node pages, in a meaningful way.
  • Allow users to search by Sacrament times, showing parishes on a map, and showing the Sacrament times in a list under the map.

I'll cover each of these important aspects of our website's functionality below.

Preliminary note: much of this code was provided originally by the great folks at Palantir, who helped us set up this and many other features on the Archdiocesan website...

Importing time information, attaching it to Parish nodes

The first step in the process is importing some 3,000+ parish event nodes (which contain data for each 'event' - the event time, the event type (Mass/Reconciliation/Adoration), whether the event is a 'Normal Service' or a special kind of Mass, the location of the event (often in a side chapel or somewhere else), the event day, and the reference for the parish to which the event is attached.

Our site uses the Migrate module to import all the data, and we have the module set up to import all the events first, then import the Parishes, attaching the events to parishes (through custom code) using a node reference.

The CSV file containing the parish event data contains over 3,000 lines of information like the following:

29,"362",1,37,48,"07:30","",0,"","English"

Our migrate import takes that line, creates a new node with the information, then later, while importing parish nodes, attaches all event nodes affiliated with that parish to the parish node itself. Then, as they say, the magic happens (via a nodereference field).

Here's the code we use to prepare our parish event node via the migrate import process:

<?php function dir_migrate_prep_parish_event(&$node, $tblinfo, $row) { // Just stick on a filler title $node-?>title = "Parish event #" . $row->peventkey; // Normalize the dates $node->field_event_start[0]['value'] = (int) substr(str_replace(':', '', $row->jos_dir_paevents_evtime), 0, 4); $node->field_event_end[0]['value'] = (int) substr(str_replace(':', '', $row->jos_dir_paevents_evendtime), 0, 4); // Add taxo from event types $mt = dir_migrate_get_map_table('pe-type'); $term = taxonomy_get_term(db_result(db_query("SELECT destid FROM $mt WHERE sourceid = %d", $row->jos_dir_paevents_fkpesptypekey))); if ($term) { $node->taxonomy[$term->tid] = $term; } // Add taxo from event days $mt = dir_migrate_get_map_table('pe-time'); $term = taxonomy_get_term(db_result(db_query("SELECT destid FROM $mt WHERE sourceid = %d", $row->jos_dir_paevents_fkevday))); if ($term) { $node->taxonomy[$term->tid] = $term; } } ?>

We basically sanitize the dates coming in from the database (we want them in standard time/0000 format), and then we add taxonomy terms to the dates.

While importing parish nodes, among other things, we attach the parish event nid to the parish node's masstimes/adorationtimes/reconciliationtimes nodereference fields:

<?php $mt = dir_migrate_get_map_table('parish-event'); $fields = dir_migrate_evtype_field_mapping(); $result = db_query("SELECT mt.destid AS nid, e.fkpettypekey AS type FROM jos_dir_parish AS p INNER JOIN jos_dir_paevents AS e ON p.pnumber = e.fkpnumber INNER JOIN $mt AS mt ON e.peventkey = mt.sourceid WHERE p.pnumber = %d", $row-?>jos_dir_parish_pnumber); while ($record = db_fetch_object($result)) { $node->{$fields[$record->type]}[] = array('nid' => $record->nid); } ?>

Displaying Event Time Information in the Nodes

To display the time information in a particular node, we simply did a bit of theming magic. It's not the most highly performant bit of code in the world, but it works.

First, we set up a field_formatter and theme function for parish event times (the following code samples are all from our site's custom.module):

<?php /** * Implementation of hook_field_formatter_info() */ function custom_field_formatter_info() { return array( 'parish_event_times' =?> array( 'label' => 'Parish Event Times', 'field types' => array('nodereference'), 'multiple values' => CONTENT_HANDLE_MODULE, ), ); } /** * Implementation of hook_theme(). */ function custom_theme() { return array( 'custom_formatter_parish_event_times' => array( 'arguments' => array('element'), ), ); } ?>

These two functions just tell Drupal that we're defining a custom display formatter for parish event times (that can be used in Views, on node teasers, and in full node displays), and then defines a theme function in which we'll tell drupal how to format everything for display.

This next function is a doozy - it basically does all the display dirtywork, and causes a performance burden on the site—if we tried displaying the mass time information for all 200 parish nodes on the site at once, the queries/processing would probably take 20-30 seconds! Therefore, we cache everything aggressively so people don't have to wait for the following theme function to do its work—after it's been done once in a day, it doesn't have to go again, as we cache the resulting page for 18 hours.

<?php /** * Theming function for the "Parish Event Times" formatter. */ function theme_custom_formatter_parish_event_times($element) { $days = array(); // @TODO - Order the $element's children by day order from the taxonomy sort, then by time // @SEE - http://archstldev.com/node/521 // Loop through all the parish event times, and build a nice array // of days and the (multiple) corresponding times foreach (element_children($element) as $key) { // Load the node $node = node_load($element[$key]['#item']['nid']); // Parse and format the time // Pad start time with leading zero if only 3 digits if (strlen($node-?>field_event_start[0]['value']) == 3) { $node->field_event_start[0]['value'] = '0'.$node->field_event_start[0]['value']; } // Account for perpetual adoration start time of '0' (midnight) if (strlen($node->field_event_start[0]['value']) == 1) { $node->field_event_start[0]['value'] = '0000'; } // Pad end time with leading zero if only 3 digits if (strlen($node->field_event_end[0]['value']) == 3) { $node->field_event_end[0]['value'] = '0'.$node->field_event_end[0]['value']; } // Account for perpetual adoration end time of '2400' (midnight) if ($node->field_event_end[0]['value'] == '2400') { $node->field_event_end[0]['value'] = '2359'; } $time = date('g:i a', strtotime($node->field_event_start[0]['value'])); if ($node->field_event_end[0]['value'] > 0) { $time .= ' – '.date('g:i a', strtotime($node->field_event_end[0]['value'])); } // Node contains taxonomy if (!empty($node->taxonomy)) { $time_data = array(); // Add event type (if not "Normal Service") foreach ($node->taxonomy as $term) { if ($term->vid == 24 and $term->name != 'Normal Service') { $time_data[] = $term->name; } } // Also add event language (if not "English") if (!empty($node->field_event_lang[0]['value']) and $node->field_event_lang[0]['value'] != 'English') { $time_data[] = $node->field_event_lang[0]['value']; } // Add event location (if any) from the field_event_loc if (!empty($node->field_event_loc[0]['value'])) { $time_data[] = 'in ' . $node->field_event_loc[0]['value'] . ''; } // Slap it on the end of the time if (!empty($time_data)) { $time .= ' ('.join(' - ', $time_data).')'; } } // Day of the week foreach ((array)$node->taxonomy as $term) { if ($term->vid == 21) { // Grab the weight of the term for sorting (see below) $days[$term->name]['weight'] = $term->weight; // Grab all the times $days[$term->name]['times'][] = $time; break; } } } // Sort the Days using the weight above (this could be improved...) // @see http://archstldev.com/node/521 asort($days); // Print the days and times $output = ''; foreach ($days as $day => $elements) { foreach ($elements['times'] as &$time) { $time = '

'.$time.'

'; } $output .= ''.$day.''; $output .= ''.implode('', $elements['times']).''; } $output .= ''; return $output; } ?>

What we basically do here is load each referenced node, then grab all the metadata for that parish event from the parish event node. Then, we display all the metadata in a nice definition list, which gets themed to look like the following:

Sacramental Time Information Display on Parish Node

Looks nice, eh? Using the asort() function, we were able to sort the times in the order of our Taxonomy listing (so we could control which days would appear first...).

Allow Users to Search by Time/Day using Views

The final step in the process was to allow users to search on the website by Mass Time (or other Sacrament times), and since we were using Views for all our other search/filtering needs, we decided to use Views to do the time search as well.

Inside our dir_migrate.module (though this could live just as easily in our custom.module), we added a views handler, "dir_migrate_views_handler_filter_inttime."

In dir_migrate/dir_migrate.module:

<?php /** * Implementation of hook_views_api(). */ function dir_migrate_views_api() { return array( 'api' =?> '2.0', 'path' => drupal_get_path('module', 'dir_migrate') . '/views', ); } ?>

In dir_migrate/views/dir_migrate.views.inc:

<?php function dir_migrate_views_handlers() { return array( 'info' =?> array( 'path' => drupal_get_path('module', 'dir_migrate') . '/views', ), 'handlers' => array( 'dir_migrate_views_handler_filter_inttime' => array( 'parent' => 'views_handler_filter_numeric', ), ), ); } function dir_migrate_views_data_alter(&$data) { $data['node_data_field_event_start2'] = $data['node_data_field_event_start']; $field = &$data['node_data_field_event_start2']['field_event_start_value']; unset($field['field'], $field['argument'], $field['sort']); $field['title'] = t('Start Time (Formatted)'); $field['help'] = t('Filter handler that translates from int storage to time of day'); $field['filter']['handler'] = 'dir_migrate_views_handler_filter_inttime'; } ?>

In dir_migrate/views/dir_migrate_views_handler_filter_inttime.inc (this is where we define our custom views filter...):

<?php class dir_migrate_views_handler_filter_inttime extends views_handler_filter_numeric { function option_definition() { $options = parent::option_definition(); $options['operator'] = array('default' =?> 'between'); $options['exposed'] = array('default' => TRUE); $options['value']['contains']['min'] = array('default' => 500); $options['value']['contains']['max'] = array('default' => 2200); return $options; } function operators() { return array( 'between' => array( 'title' => t('Is between'), 'method' => 'op_between', 'short' => t('between'), 'values' => 2, ), ); } function value_form(&$form, &$form_state) { // Get the basic loadout from the parent parent::value_form(&$form, &$form_state); $options = int_time_increments_assoc(); // Make the minor modifications $form['value']['min'] = array( '#type' => 'select', '#title' => t('Between'), '#options' => $options, '#default_value' => $this->value['min'], ); $form['value']['max'] = array( '#type' => 'select', '#title' => t('And'), '#options' => $options, '#default_value' => $this->value['max'], ); } } ?>

...and finally, some helpful functions for our integer/time CCK field/formatting, found in dir_migrate/dir_migrate.module:

<?php // ==================== CCK Bits function int_time_theme() { return array( 'int_time' =?> array('arguments' => array('element' => NULL)), 'int_time_formatter_default' => array('arguments' => array('element' => NULL), 'function' => 'theme_int_time_generic'), ); } function theme_int_time($element) { return $element['#children']; } /** * Declare information about a formatter. * * @return * An array keyed by formatter name. Each element of the array is an associative * array with these keys and values: * - "label": The human-readable label for the formatter. * - "field types": An array of field type names that can be displayed using * this formatter. */ function int_time_field_formatter_info() { return array( 'default' => array( 'label' => t('As time of day'), 'field types' => array('number_integer'), ), ); } function theme_int_time_generic($element) { return int_time_int_as_time($element['#item']['value']); } function int_time_int_as_time($int) { $string = (string) $int; if (empty($string)) { return ''; } while (strlen($string) = 12) { $ex = 'PM'; $hour -= 12; } else { $ex = 'AM'; } $hour = ltrim($hour, '0'); $hour = empty($hour) ? '12' : $hour; return "$hour:$minute $ex"; } /** * Helper function to return all possible inttimes in 15-minute increments. */ function int_time_increments() { return array( 0, 15, 30, 45, 100, 115, 130, 145, 200, 215, 230, 245, 300, 315, 330, 345, 400, 415, 430, 445, 500, 515, 530, 545, 600, 615, 630, 645, 700, 715, 730, 745, 800, 815, 830, 845, 900, 915, 930, 945, 1000, 1015, 1030, 1045, 1100, 1115, 1130, 1145, 1200, 1215, 1230, 1245, 1300, 1315, 1330, 1345, 1400, 1415, 1430, 1445, 1500, 1515, 1530, 1545, 1600, 1615, 1630, 1645, 1700, 1715, 1730, 1745, 1800, 1815, 1830, 1845, 1900, 1915, 1930, 1945, 2000, 2015, 2030, 2045, 2100, 2115, 2130, 2145, 2200, 2215, 2230, 2245, 2300, 2315, 2330, 2345 ); } function int_time_increments_assoc() { static $assoc; if (is_null($assoc)) { $assoc = array(); foreach (int_time_increments() as $int) { $assoc[$int] = int_time_int_as_time($int); } } return $assoc; } ?>

Wow... this is probably the longest post/code-dump I've ever written... sorry about that! Complex issues demand complex solutions, I guess?

Some Things Could Be Improved...

Well, actually, a lot of things could be improved. For instance, we could avoid a lot of this custom code if there were a way to create Date fields without a month or year attached—basically, a timestamp without a fully-compliant 'date' attached to it—but this is currently not possible.

Right now, I'm focusing on a few other projects, but someday I really want to tackle issue #499 on the Archdiocesan Development website: Create timefield module for Time CCK/Field. I envision a module that allows you to add time information to a node like "Saturday, from 4 p.m. to 5 p.m.," and then be able to filter Views results by time values alone... but I don't know if/when I'll get the time to do this :(

Any other thoughts or ideas?

Dec 15 2010
Dec 15

About a year-and-a-half after releasing my first contributed theme for Drupal, Airy Blue, I have finished and release my second contributed theme, MM - A Minimalist Theme.

Minimalist Theme Screenshot

MM is my first HTML5 theme, and my first for Drupal 7 (which, by the way, is awesome!). I have been working on refreshing my LLC website, Midwestern Mac, for the past few months since I scrapped my first hacked-together theme from about 2.5 years ago, and I finally decided to take the plunge and go Drupal 7 for the redesign.

MM is based on Boron, an HTML5 base theme that is still in beta for Drupal 7 (thus, I can't have a final release of my subtheme until Boron is final as well).

The theme has a few nice features:

  • No images whatsoever (cuts down on page load times, since there are less resources to load).
  • HTML5 markup (tested in IE7-9, FF 3+, Safari 4+, Chrome)
  • Progressive enhancement - we're using box-shadow, border radius, and some other CSS3 elements that only work in newer browsers at this point.

I figured I'd like to help get more themes on the docket for Drupal 7's release—right now there are very few, and I think it would be nice if people downloading D7 and wanting to tinker could have more than two or three themes to play with.

Plus, it's just a nice thing to do for an open source project that has given me a career.

Sep 04 2010
Sep 04

I came across a problem when working on a Drupal user registration form which had to include an accept terms and conditions checkbox. In fact, I came across a couple of problems, but I only had to fix one myself.

At present there are some bugs in Drupal 6 core pertaining to mandatory checkboxes. Luckily, there's a module for that. I was using the handy terms of use module, which works around the mandatory checkbox problems with or without the help of the checkbox_validate module.

One problem remained though - if I submitted the registration form without ticking the mandatory terms of use checkbox, the form validation worked in that it stopped me proceeding, and I saw the You must agree with the Terms of Use to get an account error message at the top of the form, but there was no visual highlighting of the checkbox in question.

A poke around with firebug confirmed that the form API has been cajoled into doing its form_set_error thing, and the checkbox had the error class. However, styling form elements - particularly the different types of inputs - with CSS has always been a bit hit-and-miss. The 2px solid red border that the stylesheets specified should be around the checkbox was nowhere to be seen.

I checked in several other browsers (lots of screenshots included with this post) - it looked like the IEs and Opera were the only browsers which rendered the red border around the checkbox - Firefox, Chrome and Safari did not.

My workaround is to override the theme function for a checkbox, and to wrap checkboxes with the 'error' class in a span which I can then style. In my CSS I also turn off the red border for the checkbox itself, as otherwise you get double borders in the browsers which don't have this problem in the first place:

<?php
function mytheme_checkbox($element) {
  _form_set_class($element, array('form-checkbox'));
  $checkbox = '<input ';
  $checkbox .= 'type="checkbox" ';
  $checkbox .= 'name="'. $element['#name'] .'" ';
  $checkbox .= 'id="'. $element['#id'] .'" ' ;
  $checkbox .= 'value="'. $element['#return_value'] .'" ';
  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
  $checkbox .= drupal_attributes($element['#attributes']) .' />';
 
  if (strpos($element['#attributes']['class'], 'error') !== false) {
    $checkbox = "<span class='checkbox-error'>$checkbox</span>";   
  }
 
  if (!is_null($element['#title'])) {
    $checkbox = '<label class="option" for="'. $element['#id'] .'">'. $checkbox .' '. $element['#title'] .'</label>';
  }
 
  unset($element['#title']);
  return theme('form_element', $element, $checkbox);
}

...and...

/* CSS */
.checkbox-error {
  border: 2px solid red;
}
.form-item .checkbox-error input.error {
  border: none;
}

The results are not visually perfect - the red border on my span doesn't always hug the edges of the checkbox very neatly - particularly in the browsers which don't render the red border around the checkbox, as they tend to be the ones which are rendering shadows or other fancy effects. Anyway the point is there's now something you can hang your hat on CSS-wise, and that actually shows up in all the major browsers.

Dec 20 2009
Dec 20
Blog Drupal

In Drupal it's possible to overwrite and extend a theme using sub-themes! So i usually create a custom theme for my site as a sub-Theme of a base theme and try to put all the site specific stuff to that theme instead of the base theme, although it's tricky! and sometimes i have to spend hours finding a workaround for not completely implemented sub themming features but i think it worth it. Because this way no only i can easily update the base theme but also i can patch the base theme in order to fix bugs and add new features and then contribute all this changes back to the community :)

Currently i'm using Acquia Marina as base theme on my website, I've found several bugs on this theme. fixed them and sent the patches to its issue queue. This time however i wasn't so lucky because Acquia Marina's developers are quite busy with the next major version of their awesome theme and none of the maintainer seems to have time to review and commit this patches. So I decided to share the patched version of this great theme with the community.

You can find it here : Drupal's Acquia Marina Theme version 1 patched!

Nov 22 2009
Nov 22

Over a year and a half after Drupal 6 was released, I've finally gotten around to releasing a Drupal 6 version of the Ocadia theme. The design is based on a Wordpress theme done by Beccary and subsequently ported to Drupal 4.7 by Scar_T. I came on-board after creating a Drupal 5 version of it for one of my friend's websites, Irlandia Ceramics. In fact it has been one of the main obstacles preventing me from upgrading the site to Drupal 6, but now largely thanks to peterx and sime, a Drupal 6 version is now available! Thanks guys!

The new releases can be downloaded from the Ocadia project page, while details on the changes can be found below:

Sep 23 2009
Sep 23

Creating a subtheme is a powerful and flexible way of theming in Drupal. Drupal 6 made implementing subthemes relatively easy to do. There are a few basic concepts to understand before starting, but once underway, creating a subtheme is a simple straightforward process. In this post, I'll outline the process for creating a subtheme. This is meant to be a quick primer, not an exhaustive tutorial on the elements and features of subthemes.

Before going further, I must acknowledge that this post borrows heavily from two posts currently available on Drupal.org. For further information, you can refer to:

Subtheme Concept

A subtheme is a child theme that borrows certain attributes from a parent theme. This allows you to use an established theme and a derivative of that theme both on the same site without needing to alter or duplicate the original theme.

When set up correctly, a subtheme inherits the following attributes from its parent theme:

  • All style sheets
  • All template files
  • All functions and overrides defined in the parent theme's template.php file (if it has one)
However, there are other key attributes that the subtheme does not inherit, including:
  • the parent theme's logo (logo.png)
  • regions (if the parent theme has custom regions)

Creating a subtheme

For the purposes of this post, we'll use an example of setting up a subtheme called "mysubtheme" that will be using an installed version of the contributed "framework" Drupal theme as its parent.

  1. Create a folder in your custom "themes" folder and name it the with the name you have chosen for your subtheme. Ex: mysubtheme
  2. Add a .info file to your subtheme folder. To define this as a subtheme, your subtheme folder needs text document named (using our example): mysubtheme.info
  3. Add the following elements to your .info file:
    name = mysubtheme
    description = any descriptive text you wish
    core = 6.x
    base theme = <(folder)name of any core or contributed theme or subtheme, e.g. framework>
    stylesheets[all][] = mysubtheme.css

    This defines the name of your subtheme, any description you want (shows up on the themes admin page), the reference to the base theme from which you want to inherit, and a stylesheet for defining the styles from the parent theme and other CSS that the subtheme will be overriding.
  4. Set up your regions. If the parent theme has established a set of custom regions in its .info file (many do), you will need to copy the region settings from the parent's .info file and add them to your subtheme's .info file. Otherwise, you will not be able to use these regions in your subtheme. For the contributed "framework" theme in our example, this would look like:
    regions[left] = Left sidebar
    regions[right] = Right sidebar
    regions[content] = Content
    regions[header] = Header
    regions[nav] = Navigation
    regions[footer] = Footer
    This is a feature, not a bug. It allows you to have a totally different set of regions for your subtheme if you have the need for this. If you do, just define the regions you wish to have using the "regions [regionname] = RegionLabel" syntax. If you define new regions for you subtheme, you will also need to create a new page.tpl.php file which prints these regions and place this in your subtheme folder.
  5. Add any necessary template files. As mentioned above, your subtheme will inherit the parent theme's tpl.php template files. If you wish to override a specific template file -- as in the regions example in 4 above -- add the specific tpl.php file to your subtheme folder. Alter this file as you wish. It will override the corresponding template file of the same name in the parent theme.
  6. Add your subtheme's overriding CSS file (ex. mysubtheme.css) to the subtheme folder. Use this file for all your subtheme's style overrides. You can, of course, define and add additional CSS files for your subtheme. If you wish to have multiple custom stylesheets, be sure to add reference to them in your .info file.

Having completed these steps, your subtheme should be up and ready for use. Visit your themes admin page to enable the theme.

Happy theming!

Average:

Your rating: None Average: 4.3 (56 votes)

May 29 2008
May 29

"I need to fix just this one last thing."

So, what do you do when you have 5 large-ish projects about to start in the next couple of days, you have paperwork to do, you have conference calls to attend, and cats to feed? You make a new Drupal 6 theme for Drupal.org, what else?

Apparently that is what I do rather. This is how Refresco, my new Drupal 6 theme, came about. I started poking at it about 7am, thinking I would just change a thing or two about a theme I was toying with contributing and get to work on another project, and before I knew it it was 11pm and I had submitted it. Not quite sure if anything else happened during the day...

A Very Cool Theme

Ok, I am biased, but Refresco is a killer theme. I designed it to make good use of space. For example I have a section up top to display messages that the system will on occasion give you. Well that space is currently occupied by the search bar, but are you searching when you are reading a Drupal message? Nope, so they share the space.

How about your secondary menu area, do you need to see that all the time? Not if you don't have secondary links for that page, so it just hides, same with breadcrumbs, side columns, and even one of the horizontal regions.

In all this template has 6 actual regions, and a couple of other areas that work as needed.

screenshot of refresco

One of the things I like to find when I use a template is that it is easy to modify, so that is how I make my templates, easy to change. I put all of the background color information at the top of the style sheet so that it can be gotten to easily and try to use no graphics if I can help it. I didn't use any graphics for layout or design in either of these themes, so when you change the CSS the whole look changes and you don't have to go sifting through the Photoshop jungle to fix a rounded corner or change a background graphic.

Drupal 6(mostly) Theming Notes

Some things that I need to brush up on for Drupal 6 theming (or theming in general perhaps), which I may implement more fully in Refresco and other D6 themes later:

  • theme-settings.php - Using this file and a little knowledge that I couldn't be bothered to gather in my mad rush to create the theme, I could have set the search box to be switched on, as well as the mission statement, slogan, logo, or any other normal theme setting as far as I can tell
  • Passing variables to the theme - seems like if I put a conditional statement in my preprocess function in template.php that my $vars['variable_I_want_to_set'] would just plain not get set. Not sure why. MUST figure this one out
  • The secondary links menu block is only for a menu you create or use under Menus that is named secondary links, it will NOT display your secondary links from your primary links menu, or any other menu that is filling the "secondary links" role under Menus -> Settings - maybe this is just as appropriate for D5?
  • theme('links', $primary_links) works, theme('links', $primary_links, '') does not :)

I Had a Title Here Just a Minute Ago?!?

One other gotcha, and it's one that I think also happens in Drupal 5, is that the front page didn't display my $title, because it was displaying a teaser, and not a full page. To address this I had to use the following code prepended to my node.tpl.php file:

  <?php if ($page == 0): ?>
    <h2 class="title">
      <a href="http://brauerranch.com/<?php print $node_url; ?>"><?php print $title; ?></a>
    </h2>
  <?php endif; ?>

This code ensures that if I was viewing full pages, it wouldn't print the title twice, but it did produce my title nicely on the front page, and gives a nice title link to the full content.

Other Lessons Learned

It's actually really cool that after contributing my first theme, Ranch, just a couple of days ago, that I also wrote up a handbook page called "CVS quick-start guide for theme maintainers" to help me remember how to do it, and to help others along the theme contribution path. Anyhow, I say it's cool because last night I was probably the first user of that document.

It needs a little improvement, but overall, it took me less than 1/2 hour to contribute this theme, as opposed to the several hours it took the first time. Practice makes perfect I guess.

I will be making some improvements to that handbook page, especially mentioning Drupal 6 tagging/branching specifics, as well as the formats for the $Id$ and that they should be included in your source files.

Well, I think that's enough for now. I have to get going to work, right after I tweak just this one thing...

May 25 2008
May 25

Extra Extra - New Theme Created for Drupal Contributions Repository!

Ok maybe it is not a big deal to most people, but because it was my first Drupal project, it really is big news for me. So big that I am still riding high on my pink Drupal drop shaped cloud. See, yesterday I officially released a new theme for Drupal 5 (soon to make it 6 as well) called Ranch, and put it out there for all to download and use.

Ranch started out life based on a theme that I had put together using NiftyCorners for a client of ours, which I had built upon Zen. It is nothing like the former site, aside from being valid and clean, and very rounded. It is pretty nice, with a very clean CSS based layout, 2 columns with a Mission Statement area, primary and secondary links, breadcrumbs, as well as sidebar for whatever your heart desires.

I chose to use Zen mostly because everyone was talking about it, and because it was supposed to be easier to create a theme from than by doing it from scratch. I don’t really agree with that last part, though I do like how Zen does many things, especially their CSS where they lay out most of the selectors so that when you are using FireBug to change or fix something, you can actually alter it (using the empty CSS tag that is provided) right there, quickly and easily. Very nice.

I think for my next theme however, that I will borrow the CSS, and do the rest from scratch. In the end I deleted hundreds of lines of code that I was never going to use and just sort of seemed like bloat.

Giving it to Drupal

Yesterday I finished (ok, stopped) cleaning, tweaking, and perfecting my theme. I might be a little bit... “particular” about things being just right, so it is sometimes a challenge for me to say something is good enough. Anyhow, after finishing up Ranch, it was time to create a new project for it on Drupal.org, and to create a release so that others could easily download and use it.

This is easier said than done. Here is a breakdown of what needed to be done, and a little of the mistakes I made trying to do it.

CVS Account on Drupal.org

The very first thing I needed to do was to request CVS access so that I could contribute to Drupal.org. I did this by going to drupal.org/cvs-application and filing out the application as well as I could. I have it on good authority that if you have a good theme that doesn’t look like crap, that you could probably lorem ipsum your application with a link to the theme and you may actually get approved :) Ok, lorem ipsum might not go over, but you might be able to get away with Dr. Suessing it. Haiku would be going to far.

Read a LOT - Export - Login - Add - CVS is FUN!

Next thing I had to do was find a bunch of information on Drupal.org on how to correctly CVS, make releases, branch, tag, etc. Luckily this is all very well documented and I had in person help from Josh Brauer, but it was still a bit of work. What it all boils down to are the next several lines of bash history:

mkdir ~/Sites/contrib
cd ~/Sites/contrib/
export CVSROOT=:pserver:[email protected]:/cvs/drupal-contrib
cvs login
cvs checkout -l contributions/themes

WARNING - Don't use cvs checkout -l contributions/modules

Right here I was led astray by another document, and accidentally used contributions/modules instead of contributions/themes. This of course is a theme, so this didn't work. So I edited it back to where I removed everything, and started over with the contributions/themes. Also, make note in this next section that the -l is REALLY important

cd contributions/themes/
cp -R /Library/WebServer/Documents/client/drupal-5.7/sites/default/themes/ranch ranch
cd ranch/
ls -al

Yep, everything is there (including a couple of things that I don’t want there, which tosses me a few issues later)

cd ..
cvs add ranch
cvs add ranch/*
cvs commit -m "Initial commit of the Ranch theme, a standards compliant CSS theme with easy to change color."

This is the initial commit, which created the “HEAD” of my project. Neat! Too bad it’s screwed up at this point, but we fix it later. Next is were we create a branch called DRUPAL-5 - can’t have a project release without this!

cd ..
cvs tag -b DRUPAL-5 themes/ranch
cd themes/ranch/
cvs update -dP -r DRUPAL-5

Here is where I was saying it was screwed up. See, CVS isn’t recursive, BUT it will add directories that are in the current directory. So, what ended up happening is that it added my images directory, and my sourcefiles directories. So here is me fumbling around trying to make it right. Actually, suffice to say that I remove, cvs delete, recreate, re-checkout, start from scratch, do all sorts of crap until I figure out that this is what I have to do:

cd ../../..
rm -Rf contributions/
cvs checkout -d ranch contributions/themes/ranch
cd ranch
cvs delete images
cvs delete sourcefiles
cvs commit -m "removing unused directories"
cvs status

As Josh says “CVS Status is your BFF!”

cd ..
rm -Rf ranch/
cvs checkout -r DRUPAL-5 -d ranch contributions/themes/ranch/
cd ranch/
cvs tag DRUPAL-5--1-0

This is where I made my first release tag - This makes the whole package thing possible. Done with CVS for now, YAY!

Make The Project

Now that I had gotten so much pleasure (not to mention knowledge) from my CVS time with Drupal, it was time to actually create my Ranch project. I did this by going to Drupal.org, logging in, clicking on Create Content on the right sidebar, and selecting Project.

Before I got too far on that though, I needed to make a good screenshot for my project page. I followed these guidelines: http://drupal.org/node/11637

After making the nifty screenshot, I finished filling out my Project page, then I went and created a release, which you do by going to the link in the right sidebar called My projects, finding my project amongst the very long list of one, and clicking on “Add release” under the Project links.

From there I selected the DRUPAL-5--1-0 CVS Identifier (I think, I don’t have a history on this part and being an old fart, my addled brain doesn’t remember exactly) and hit Next.

It was pretty self explanatory after that. Then, after waiting just a minute, I checked, and voila my project page was complete. That’s about the time I was starting to say things like Woot! a whole lot, and dancing and basically making a spectacle of myself. It wasn’t pretty. At least I was happy though.

And that is how I made my first Drupal project.

Pat Teglia - CrashTest_

May 20 2008
May 20

One of my recent projects has been to help move a great Drupal theme forward. Light Fantastic began life as a Summer of Code project in 2007. Amila Sampath, aka lucksy, put together a theme that is great. There are some things remaining to be done, but at the very least there is now a development tar file available for download. It has been a couple of months since most of my work with it so I'll have to go back and revisit the changes that are necessary to polish it off. I've got some ideas for customizing the theme kicking around in the noggin but we'll see how much time those get in the days coming up. If you like the looks of it be sure to download a copy and report what you find.

  • Sitewide Tags:
Jan 14 2007
Jan 14

The upgrade went quite nice, even though I had to upgrade several modules and port quite a bunch of custom hacks I had on my old (Drupal 4.6) site. I first upgraded to 4.7, then to 5.0 (as is the recommended procedure) on a test-site, and after figuring out how to fix or work-around all the issues that appeared, I upgraded the live site.

I password-protected the site during the upgrade, that's why it wasn't available for a while today (and caused some problems on Planet Debian it seems, sorry for that!).

New features you might enjoy:

  • Every blog post, podcast entry, and photo/image has an AJAX-enabled voting/rating box now, thanks to the nice jRating module. Feel free to rate any content over here, I'm eager to know what you think.
  • The Service links module provides those tiny images in each post, which allow you to submit the post to del.icio.us, Digg, etc. etc. with a single click.
  • I now use the great new standard Drupal-Theme Garland with a custom color map. I really like it.
  • Tons of small changes here and there, removing custom hacks which are now obsoleted with the new Drupal release, shuffling some menus around etc. etc.

If you notice any bugs or problems with the site, please let me know.

Aug 22 2005
Aug 22

I played a bit with the colors of my blog theme. I'm no usabiliy expert, but I think it's a bit cleaner now and looks better.

Please report if I have broken something.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web