2. Working with JavaScript in Drupal – Drupal 6 JavaScript and jQuery

Chapter 2. Working with JavaScript in Drupal

The first chapter in this book introduced Drupal and JavaScript. It also explained the role that JavaScript plays in the Drupal 6 environment. We will now move beyond mere explanation and take a look at the practical details and examples.

In this chapter, we will be working with JavaScript inside of a Drupal environment. We will begin by exploring how JavaScript is included in Drupal pages, and then create our first script for Drupal. While we're not going to cover the basics of the JavaScript language (there are already lots of available resources on the topic), the code we create here will be simple and straightforward.

The purpose of this chapter is to cover the basics on how JavaScript can be used within Drupal 6. In that regard, this chapter will serve as a foundation for our future JavaScript development. Here are the topics that we're going to cover:

  • Serving JavaScript from Drupal

  • Creating a first script

  • Creating a simple theme

  • Adding JavaScript to a theme

Without further ado, let's get going.

How Drupal handles JavaScript

How is JavaScript typically used? Mostly, it is used to provide additional functionality to a web page, which is usually delivered to a web browser as an HTML document. The browser receives the HTML from the server and then begins the process of displaying the page. During this parsing and rendering process, the browser may request additional resources from the server such as images, CSS, or Flash. It then incorporates these elements into the document displayed to the user.

In this process, there are two ways that JavaScript code can be sent from the server to the browser. First, the code can be placed directly inside the HTML. This is done by inserting code inside the <script> and </script> tags:

<script type="text/javascript">
alert('hello world');
</script>

This is called including the script inline.

Second, the code can be loaded separately from the rest of the HTML. Again, this is usually done using the <script> and </script> tags. However, instead of putting the code between the tags, we use the src attribute to instruct the browser to retrieve an additional document from the server.

<script type="text/javascript" src="some/script.js"></script>

In this example, src="some/script.js" points the browser to an additional script file stored on the same server as the HTML document in which this script tag is embedded. So, if the HTML is located at http://example.com/index.html, the browser will request the script file using the URL http://example.com/some/script.js.

Note

The </script> tag is required

When XML was first standardized, it introduced a shorthand notation for writing tags that have no content. Instead of writing<p></p>, one could simply write<p/>. While this notation is supported by all modern mainstream browsers, it cannot be used for<script></script> tags. Some browsers do not recognize<script/> and expect that any<script> tag will be accompanied by a closing</script> tag even if there is no content between the tags.

If we were developing static HTML files, we would simply write HTML pages that include <script></script> tags anytime we needed to add some JavaScript to the page. But we're using Drupal, not static HTML, and the process for adding JavaScript in this environment is done differently.

Where Drupal JavaScript comes from?

As with most web content management systems, Drupal generates HTML dynamically. In the previous chapter, we talked about how this is done through interactions between the Drupal core, modules, and the theme system. A single request might involve several different modules. Each module is responsible for providing information for a specific portion of the resulting page. The theme system is used to transform that information from PHP data structures into HTML fragments, and then compose a full HTML document.

But this raises some interesting questions: What part of Drupal should be responsible for deciding what JavaScript is needed for a page? And where will this JavaScript come from?

In some cases, it makes sense for the Drupal core to handle JavaScript. It could automatically include JavaScript in cases where scripts are clearly needed.

JavaScript can also be used to modify the look and feel of a site. In that case, the script is really functioning as a component of a theme. It would be best to include the script as a part of a theme.

JavaScript can also provide functional improvements, especially when used with AJAX and related technologies. These features can be used to make more powerful modules. In that case, it makes sense to include the script as a part of a module.

So which one is the best: modules, themes, or core? Rather than deciding on your behalf, Drupal developers have made it possible to incorporate JavaScript into all three:

  • The Drupal core handles including the core JavaScript support as needed. The Drupal and jQuery libraries are included automatically when necessary.

  • When theme developers needs to add some JavaScript, they can do so within the theme. There is no need to tamper with the core, or to accompany a theme with a module.

  • Finally, module developers can add JavaScript directly to a module. In this way, modules can provide advanced JavaScript functionality without requiring modification of the theme.

In this book we will add scripts to themes and modules. As we get started with this chapter, we will begin with a theme.

Note

Module or theme?

How do you decide whether your script ought to go in a theme or in a module? Here's a basic guideline. If the script provides functionality specific to the layout details of a theme, it should be included in a theme. If the script provides general behavior that should work regardless of the theme, then it should be included in a module.

Sometimes it is hard to determine when a script belongs to a theme and when it should to be placed in a module. In fact, the script we create here will be one such a case. We are going to create a script that provides a printer-friendly version of a page's main content. Once we have the script, we will attach it to a theme. Of course, if we want to provide this functionality across themes, we might instead create a module to house the script.

Since modules require some additional PHP development, we will delay examining them until Chapter 8. We will start out simply with a JavaScript-enabled theme.

Project overview: printer-friendly page content

As we continue through this book, each chapter will have at least one project. In this chapter, we are going to write one piece of JavaScript and then create a theme to utilize the JavaScript.

The JavaScript that we will write creates a pop-up printer-friendly window, and automatically launches the print dialog. This is usually launched from File | Print in your browser's menu.

Once we write the script, we will incorporate it into a theme, and add a special printing feature to the page(s) displayed with that theme. As we walk through this process, we will also create our first theme. (Technically, it will be a subtheme derived from the Bluemarine theme.)

By the end of this project, you should know how to create Drupal-friendly JavaScript files. You will also know how to create themes and add scripts to them. These are foundational tasks upon which we will build in subsequent chapters.

The first step in the process is to write the JavaScript.

The printer script

Our script will fetch the main content of a page and then open a new window, populating that window's document with the main content of the page. From there, it will open the browser's print dialog, prompting the user to print the document.

Since this is our first script, we will keep it simple. The code will be very basic, employing the sort of classical procedural JavaScript that web developers have been using since the mid-1990's. But don't expect this to be the norm. In the next chapter we will dive into what John Resig, creator of jQuery, calls the "New Wave JavaScript."

To minimize clutter and maximize the reusability of our code, we will store this new script in its own script file. The file will be named printer_tool.js:

// $Id$
/**
* Add printer-friendly tool to page.
*/
var PrinterTool = {};
PrinterTool.windowSettings = 'toolbar=no,location=no,' +
'status=no,menu=no,scrollbars=yes,width=650,height=400';
/**
* Open a printer-friendly page and prompt for printing.
* @param tagID
* The ID of the tag that contains the material that should
* be printed.
*/
PrinterTool.print = function (tagID) {
var target = document.getElementById(tagID);
var title = document.title;
if(!target || target.childNodes.length === 0) {
alert("Nothing to Print");
return;
}
var content = target.innerHTML;
var text = '<html><head><title>' +
title +
'</title><body>' +
content +
'</body></html>';
printerWindow = window.open('', '', PrinterTool.windowSettings);
printerWindow.document.open();
printerWindow.document.write(text);
printerWindow.document.close();
printerWindow.print();
};

Since this is our first piece of Drupal code, we are going to dwell on the details a little more than we will in future sections.

First, let's talk about some of the structural aspects of the code.

Drupal coding standards

In general, well-formatted code is considered a mark of professionalism. In an open source project such as Drupal, where many people are likely to view and contribute to the code, enforced coding standards can make reading and understanding what the code does easier.

When contributing code to the Drupal project, developers adhere to a Drupal coding standard (http://drupal.org/coding-standards). Add-on modules and themes are expected to abide by these rules.

It is advised that you follow the Drupal standards even in code that you do no anticipate submitting to the Drupal project. Along with keeping your code stylistically similar to Drupal's, it will also help you develop good coding habits for those occasions when you do contribute something to the community.

For the most part, the official Drupal coding standards are focused on the PHP code. But many of these rules are readily applicable to JavaScript as well. Here are a few important standards:

  • Every file should have a comment near the top that has the contents $Id$. This is a placeholder for the version control system to insert version information.

    Note

    Drupal uses CVS (Concurrent Versioning System) for source code versioning. Each time a file is checked into CVS, it will replace $Id$ with information about the current version of the software. To learn more about CVS, visit http://www.nongnu.org/cvs/.

  • Indenting should be done with two spaces (and no tabs). This keeps the code compact, but still clear.

  • Comments should be used wherever necessary.

    • Doxygen-style documentation blocks (/** ... */) should be used to comment files and functions.

    • Any complex or potentially confusing code should be commented with // or /* ... */.

    • Comments should be written in sentences with punctuation.

  • Control structure keywords (if, else, for, switch, and so on) should appear at the beginning of a line, and be followed by a single space (if (), not if()). Here's an example:

    if (a) {
    // Put code here.
    }
    else if (b) {
    // Put code here.
    }
    else {
    // Put code here.
    }
    
  • Operators (+, =, *, &&, ||, and so on) should have a single space on each side, for example: 1 + 2. The exception to this rule is the member operator (.), which is used to access a property of an object. There should be no spaces surrounding these. Example: window.document (never window . document).

Stylistic differences between PHP and JavaScript

Not all PHP coding standards apply to JavaScript. PHP variables and function names are declared in all lower case with underscores (_) to separate words. JavaScript typically follows different conventions.

JavaScript variables and functions are named using camel case (sometimes called StudlyCaps). For a variable or function, the first word is all lower case. Any subsequent words in the variable or function name are capitalized. Underscores are not used to separate words. Here are some examples:

var myNewVariable = "Hello World";
function helloWorld() {
alert(myNewVariable);
}

While this convention is employed throughout the Drupal JavaScript code, there is currently no hard-and-fast set of JavaScript-specific coding conventions. The working draft, which covers most of the important recommendations, can be found at http://drupal.org/node/260140.

Here is a summary of the more important (and widely followed) conventions:

  • Variables should always be declared with the var keyword. This can go a long way towards making the scope of variables explicit. As we will see later in the book, JavaScript has a particularly broad notion of scope. Functions inherit the scope of their parent context, which means a parent's variables are available to the children. Using var makes it easier to visually identify the scoping of a variable. It also helps to avoid ambiguous cases which may lead to hard-to-diagnose bugs or issues.

  • Statements should always end with a semicolon (;). This includes statements that assign functions, for example, myFunction = function() {};. Our print function, defined earlier, exhibits this behavior.

    Note

    Why do we require trailing semicolons?

    In JavaScript, placing semicolons at the end of statements is considered optional. Without semicolons, the script interpreter is responsible for determining where the statement ends. It usually uses line endings to help determine this. However, explicitly using semicolons can be helpful. For example, JavaScript can be compressed by removing whitespace and line endings. For this to work, every line must end with a semicolon.

  • When an anonymous function is declared, there should be a space between the function and the parentheses, for example, function () {}, not function() {}. This preserves the whitespace that would be there in a non-anonymous function declaration (function myFunction() {}).

There are other conventions, many of which you will see in this book. But the ones mentioned here cover the most frequently needed.

With coding standards behind us, let's take a look at the beginning of the printer_tool.js file.

The first lines

Let's take another look at the first ten lines of our new JavaScript:

// $Id$
/**
* Add printer-friendly tool to page.
*/
var PrinterTool = {};
PrinterTool.windowSettings = 'toolbar=no,location=no,' +
'status=no,menu=no,scrollbars=yes,width=650,height=400';

The first line is a comment with the $Id$ tag required by the coding standards. If this file were checked into CVS, the line would be replaced with something like this:

// $Id: print_tools.js,v 1.0 2008/07/11 08:39 mbutcher Exp $

As you can see, CVS will add some information about the version of the file. This information includes the name of the file, its version number, when it was checked in, and who checked it in.

Directly beneath the ID comment is the file-wide documentation block.

Documentation blocks use a special comment style beginning with a slash and two asterisks: /**. Automated documentation tools can later scan the file and pick out the documentation blocks, automatically generating API documentation for your script.

The role of the file-wide documentation block is to explain what the code in the file does. The first line should be a single-sentence description of the file. Additional paragraphs may be added.

In the following line, we define our PrinterTool object:

var PrinterTool = {};

This code is declaring the PrinterTool variable and assigning it an empty object literal ({}). This line plays an interesting role in the structure of our application, and we will see constructs like this both within Drupal and in the later chapters of this book.

Note

An object literal is a notation for defining an object by using its symbolic delimiters, rather than by using a new constructor. That is, instead of calling the new Object() constructor, we use the symbolic representation of an empty object, {}, to declare an empty un-prototyped object. We will use this method frequently in this book.

The role of the PrinterTool object is to serve as a namespace for our application. A namespace is an organizational tool that allows the software developer to collect various resources together . This is done without having to worry that these resources will be in conflict with those created by other developers.

Note

Objects that function as namespaces should always begin with an initial capital letter, such as Drupal or PrinterTools.

Let's consider an example. The main function in our printer_tool.js file is named print(). But print is a very common name, and the built-in JavaScript window object already has a function named print(). So how do we distinguish our print() from window's print()?

One popular method of solving this problem is to assign objects to namespaces. Then the developer can explicitly specify which print() ought to be used.

Let's look at the next line of the script for an example:

PrinterTool.windowSettings = 'toolbar=no,location=no,' +
'status=no,menu=no,scrollbars=yes,width=650,height=400';

Here we create the windowSettings string object, assigning it a long value that will later be used when calling JavaScript's built-in window.open() function.

But windowSettings is defined as a member of the PrinterTool namespace.

If we were to insert the following code directly beneath the previous line, what would happen?

alert(windowSettings);

We would get an error since there is no object in the current context named windowSettings. To retrieve the value of the windowSettings object, we would need to write this instead:

alert(PrinterTool.windowSettings);

Now the alert dialog would be created and populated with the string'toolbar=no,location=no,status=no...'.

That is how namespaces function. If we were to call print(), it would use the window.print() function. Remember, window is the default scope for browser-based JavaScript. To call the print() function, which this script defines, we would have to provide the full namespace PrinterTool.print().

Since we are talking about it already, let's take a closer look at the PrinterTool.print() function.

The print() function

The PrinterTool.print() function looks like this:

/**
* Open a printer-friendly page and prompt for printing.
* @param tagID
* The ID of the tag that contains the material that should
* be printed.
*/
PrinterTool.print = function (tagID) {
var target = document.getElementById(tagID);
var title = document.title;
if (!target || target.childNodes.length === 0) {
alert("Nothing to Print");
return;
}
var content = target.innerHTML;
var text = '<html><head><title>' +
title +
'</title><body>' +
content +
'</body></html>';
printerWindow = window.open('','',PrinterTool.windowSettings);
printerWindow.document.open();
printerWindow.document.write(text);
printerWindow.document.close();
printerWindow.print();
};

The function starts with a documentation block. As with a page-level documentation block, this begins with a single sentence describing the function. If more information is needed, we could include additional sentences after this first line.

In this documentation block, we also have a special keyword @param. This indicates to the documentation processor that we are about to describe one of the parameters for this function. The @param keyword should be followed by the names of the arguments it describes. In our case, there is only one param, tagID. These are the only two things that should be on this line.

The next line should be indented two more spaces, and should describe the parameter.

Note

Order Matters

@param tags should always describe arguments in order. If we have a function with the signature myFunction(paramA, paramB), then the documentation block should have the @param paramA section before the @param paramB section.

Our function here does not have a return value. If a return value were to exist, that too would need to be documented. Consider this example function:

function sum(a, b) {
return a + b;
}

The documentation block for such a function might look like this:

/**
* Add two numbers.
*
* This function adds two numbers together and returns
* the sum.
*
* @param a
* The first number.
* @param b
* The second number.
* @return
* The sum of a and b.
*/

An automated documentation tool, such as Doxygen, can use such a well-formatted comment to create a helpful API reference.

Let's continue and look at the next part of the code.

First, we assign a function to PrinterTool.print:

PrinterTool.print = function (tagID) {

Essentially, what we have done is created a method named print() attached to the PrinterTool object. The function takes one argument: tagID. This will be the ID of an element in the HTML document.

Note

A function defined with the form name = function () {} is called a function expression. For all intents and purposes, it works the same as a typical function declaration of the form function name() {}. The subtle differences are explained in the official Mozilla JavaScript documentation: http://developer.mozilla.org/En/ Core_JavaScript_1.5_Reference:Functions.

Inside the function, we begin by getting information from the document that the browser is currently displaying:

PrinterTool.print = function (tagID) {
var target = document.getElementById(tagID);
var title = document.title;
if (!target || target.childNodes.length === 0) {
alert("Nothing to Print");
return;
}
var content = target.innerHTML;

There are two major pieces we want to retrieve from the document. These pieces are the title of the document and the contents of some specified element. We start by finding that element.

To find the element, we search the document for an element with an ID passed in as tagID. The DOM (Document Object Model) API, which defines standard objects that describe HTML and XML documents, provides a method called getElementById(). It searches the DOM for an element with the given ID, and returns the element if it is found.

Note

The DOM API is standardized by the World Wide Web Consortium (http://w3.org), and is implemented by all major web browsers. With the DOM, we can manipulate HTML and XML documents from JavaScript.

We store the desired element in the target variable. We then get the title of the current document.

Next, we check to make sure that target is set and that it has content. This is done using the conditional if (!target || target.childNodes.length === 0). If target is null or undefined, !target will return true. If the target element has no children, then the childNodes.length will be 0. In either of these circumstances, the function will alert the user of the problem and return without opening a printer-friendly page.

Note

Strong Equality and Type Coercion

When comparing two objects for equality in JavaScript, we usually do something like this: if (a == b) { /* do something */ }. In this case, the JavaScript interpreter will try to convert both a and b to the same type before comparing them. So the string "0" is equal to the integer 0. Often times this is good. However, sometimes coercion can cause problems, as it might give the faulty impression that two values are equal when they are not. To avoid this problem, use the strong equality operator (===). As a programmer, you should keep this difference in when as you write your code.

Once the script has made it beyond this test, we know there is content inside of the target element. We want the content of target to be a string (rather than as mere DOM objects), so we access that information using target's innerHTML property.

At this point, we have the two major pieces of information we need: the title of the page and the content that we want to print.

Next, we want to put this information into a new window and prompt the user to print the contents of that window:

PrinterTool.print = function (tagID) {
var target = document.getElementById(tagID);
var title = document.title;
if(!target || target.childNodes.length == 0) {
alert("Nothing to Print");
return;
}
var content = target.innerHTML;

var text = '<html><head><title>' +
title +
'</title><body>' +
content +
'</body></html>';
printerWindow = window.open('', '', PrinterTool.windowSettings);
printerWindow.document.open();
printerWindow.document.write(text);
printerWindow.document.close();
printerWindow.print();

}

The portion we are concerned with is highlighted in the function.

First, we create the text variable, which holds the HTML for our new printer-friendly version. This document is sparse. All it has is a title, the content that we want to print, and the required HTML tags.

Next, we open a new window with window.open(). This is where we use the PrinterTool.windowSettings property that we defined earlier. The new window will have a default blank document. We open that document for writing (printerWindow.document.open()), write text to it, and then close it for writing.

Now we have a new window with the content that we want to print. The last highlighted line, printerWindow.print(), opens the printer dialog.

Our first JavaScript tool is now written. Next, we will create a new theme and incorporate this tool into the theme.

Creating a theme

As we saw in Chapter 1, Drupal separates layout and styling information from processing code. HTML is usually stored in templates or theme functions. The CSS along with other styling information (including some images) are also stored separately from the functional code.

A theme is a collection of resources, (usually template files, CSS, JavaScript, and images) that can be plugged into Drupal to provide layout and styling to a site.

If we want to change the look and feel of a site, the best place to start is with a theme.

W've already created a JavaScript file that provides additional printing functionality. In this section, we are going to create a new theme, and then incorporate our new script.

Typically, a theme must provide the following things:

  • HTML markup for common Drupal structures such as pages, blocks, comments, and nodes. This will include navigational elements.

  • Any styles needed. This is typically done in the CSS files.

  • Any necessary images or media elements that will play a substantial role in layout.

  • Information about the theme, including a screenshot.

In addition to these, many themes will also provide:

  • JavaScript files that may be necessary for added functionality.

  • Other sorts of media, such as Flash animations, may occasionally be needed.

  • PHP code that performs complex layout tasks may sometimes be used.

A theme must have at least one pre-defined file (the theme's .info file). Commonly though, full themes have eight or more files.

Full themes and subthemes

The first step in creating our theme is deciding whether we want to start from scratch or begin with an existing theme. If we were to start from scratch, we would create a full theme. But if we wanted to build on another theme, we could create another kind of theme called a subtheme.

To create a full theme, we would need to implement all of the required features of a theme, and perhaps add on some other features as well. Typically, this would involve creating all of the necessary templates, a couple of CSS files, and a couple of helper files.

Sometimes, it is more expedient to begin with an existing theme and just override the things we want to change. This is the capability that subthemes, a new addition in Drupal 6, provide.

From a technical perspective, creating a full theme is not difficult, but it is time-consuming. In contrast, a subtheme can be created quickly. Since our focus is on JavaScript, and not theming, we will be creating a subtheme. That way, we can make the most of an existing project and keep our work to a minimum.

As the name implies, a subtheme is derived from another theme. Therefore, we will need to pick a theme to start with. Drupal comes with six themes pre-installed, and these vary in method and complexity. For example, Garland is a complex theme with templates, JavaScript, CSS, and lots of special PHP. In contrast, Chameleon generates simpler HTML, but does all of this in pure PHP code, without reliance on template files.

Since we want to focus our attention on JavaScript, it would be best to start with a simple theme. From there, we will selectively override only what we need.

Our theme of choice will be the Bluemarine theme, which has a very basic PHPTemplate-based structure that is easy to customize.

Note

Looking for a good base theme to start with? Check out the Zen theme in the contributed themes at http://drupal.org/project/zen. It's built to enable subtheming. You will need to create some CSS content, but the HTML structure is all in place.

We will with Bluemarine and create a new subtheme, borrowing as much as possible from the base theme.

Creating a theme: first steps

To create a theme, we will do the following:

  1. Create a directory for the theme.

  2. Create the theme's .info (dot-info) file.

  3. Add our first files.

After we've finished these three short steps, we will add our JavaScript to the theme.

Note

A precaution for theme developers

To get build our theme correctly, we will need to be able to view it. But to view it, we will need to have it enabled. What if we make a mistake that prevents Drupal from rendering correctly? We could lock ourselves out of the administration page. To prevent this from happening, it is wise to set the administration theme to one of the default themes. This is done in Administer | Site configuration | Administration theme.

Creating a theme directory

Every theme should have its own directory.

When you install Drupal, one of the directories created is called themes/. If you take a look inside that directory, you will see all of the top-level (non-subtheme) themes that Drupal provides. Do not put your themes in there. This directory is only for themes that come with Drupal's core.

Themes, like modules, go inside the sites/ subtree. The sites/ area also appears inside the Drupal directory and is created when you install Drupal. Inside the sites/ directory, two folders are created by default: sites/all/ and sites/default/.

To better understand these two directories, keep in mind that one installation of Drupal can serve multiple sites. For example, if I have a site called example.com and a site called anotherexample.com, I can use one installation of Drupal to serve both.

The first site I install will be installed in sites/default/. The next site I install will need to go in its own folder (for example, sites/anotherexample.com/). Content that only belongs to a single site should go in that site's directory.

For example, if I want to install a special theme for anotherexample.com, I should put the theme in sites/anotherexample.com/themes/.

In other cases, I may want to share a theme or module across all sites. In these cases, files would go in sites/all/. In this book, we will be putting all of our themes and modules in sites/all/themes/ and sites/all/modules/.

Note

If in doubt, put files in sites/all/. This makes it easier to share your work between sites.

With that background material behind us, let's create our theme directory. The name we give to this directory will be the name of our theme.

Note

Theme and module names should always be in a lower case, and may be composed only of letters, numbers, and underscores. In the .info file, we will be able to attach a human-readable name to the theme. That may use spaces, capital letters, and other special characters.

Our first theme will be in sites/all/themes/frobnitz/ as seen in the following screehshot:.

If this is the first theme you create, you may also need to create the sites/all/themes/ directory.

Once the directory is created, we need to add a special file to tell Drupal about the theme.

Creating the .info file

Inside sites/all/themes/frobnitz/, we need to create a file to provide important information about our theme. The file will always be named after the theme, and end with the extension .info. Because of the extension, it is usually called the theme's dot-info file.

We will create frobnitz.info, and add the following contents to the file:

; $Id$
name = Frobnitz
description = Table-based multi-column theme with JavaScript
enhancements.
version = 1.0
core = 6.x
base theme = bluemarine

The .info file contains a handful of lines with the form name = value. These entries provide basic information about the theme.

Some of this information is displayed to the user (for example, name and description). Some information is used by Drupal to make sure that this theme will work with the installed version of Drupal. The core parameter is used for that.

Later in this chapter, we will see some other entries that contain information directly related to the display of the theme. With those parameters, we can change the way the theme looks and behaves just by altering the values.

The name, description, version, and core fields are required for all themes.

  • The name field is used to give our theme a human-friendly name. You can use capital letters and spaces in this field.

  • The description parameter is used to provide a one-sentence explanation of what the theme does.

  • The version field should indicate which version number of this theme is. As with most software, you typically start with 1.0.

  • Finally, the core field should indicate what version of Drupal this theme works with. For us, it will always be 6.x.

There's one additional parameter in our file:

base theme = bluemarine

The base theme parameter is what we use to inform Drupal that our theme is a subtheme, derived from bluemarine. If we were creating a theme from scratch, we would not include this line.

For the time being, this is all we need in our theme file. Later, we will add more.

Note

Modifying .info files and clearing the cache

To improve performance, Drupal caches theme information, particularly the theme's .info file. When you change the contents of that file (for example, when you add a new script or stylesheet), you will need to clear the theme information cache to force Drupal to re-read the .info file. The most reliable way to do this is through Administer | Site configuration | Performance. At the bottom of that page is a button labeled Clear cached data. Press that button to clear the cache.

Adding files to the theme

At this point, we've actually created a working theme. Only the theme's directory and .info file are required. With just those two elements, we can now go to Administer | Site building | Themes and select our Frobnitz theme.

Of course, all Frobnitz will be at this point is an exact duplicate of Bluemarine The following screenshot shows a sample of the Frobnitz theme:

The logo image, titles, and all other information in the screenshot is showing through the regular site configuration. The look and feel should be identical whether we choose Bluemarine or our new Frobnitz style.

What we want to do now is add something new to our theme, and what better place to start than with a stylesheet.

Our theme will import all of the stylesheets of its parent. So in our theme, we inherit style.css from Bluemarine. Looking at the HTML source for a page rendered with Frobnitz, we would see a line like this:

<link type="text/css" rel="stylesheet" media="all"
href="/drupal/themes/bluemarine/style.css" />

If we didn't want that style to be loaded from Bluemarine, we could simply create another file named style.css in our own theme's directory. This new file would override Bluemarine's.

But we don't want to start over and rebuild the stylesheet. We just want to add a few extra styles. To do this, we will create a new stylesheet called frobnitz.css. This CSS file will also go inside our sites/all/themes/frobnitz/ folder.

Note

Like PHP and JavaScript, the Drupal project defines coding standards for CSS. You can learn more about these here: http://drupal.org/node/302199.

To begin, all we will do is add a black, one-pixel border on the right side of the lefthand column. The stylesheet looks like this:

#sidebar-left {
border-right: 1px solid black;
}

Note

Cascade: the 'C' in 'CSS'.

Drupal will add styles in a specific order, with theme styles added last. Because of this, you can predictably make use of the CSS cascading behavior. The previous declaration will be added to the declaration made in Bluemarine's style.css file. That means we will get the combination of styles in style.css and frobnitz.css, with frobnitz.css's declarations taking precedence.

But before our new stylesheet will have any effect, we need to tell Drupal to include it as part of the theme. This is done with a simple addition to the frobnitz.info file:

; $Id$
name = Frobnitz
description = Table-based multi-column theme with JavaScript enhancements.
version = 1.0
core = 6.x
base theme = bluemarine
stylesheets[all][] = frobnitz.css

Only that last line, which is highlighted, is different. This informs Drupal that there is a stylesheet that should be used on all format types for this page, and is named frobnitz.css.

The all keyword indicates that this stylesheet applies to all media format types. CSS format types include print (for printed media), screen (for screen displays), and other types.

Note

While this directive uses an array-like syntax, it does not function like an array. You cannot, for example, refer to stylesheets[all][1].

The empty square brackets on the end emulate the PHP array assignment operation.This stylesheet is added to a list of stylesheets. This array-like syntax, which we will see again in the next section, indicates that an attribute can be used multiple times. For example, we could add another stylesheet using stylesheets[all][] = anotherstyle.css.

Note

The base path for a stylesheet included using stylesheets[][] is always the path of the theme. In other words, stylesheets[all][] = frobnitz.css will point to sites/all/themes/frobnitz/frobnitz.css.

Remember to clear the theme cache to see the updates after changing this file (see the information box a few pages back).

Now, after clearing the cache, we can see the results of our labor:

Notice the black line between the lefthand menu and the main page contents. That's what we added with our new stylesheet.

These last three segments were a rushed tour through the process of creating a new theme. We will continue to build on our theme throughout this book. In the next section, we will work with theme templates and theme JavaScript. However, this is not a comprehensive introduction to themes.

If you are interested in learning more about theming in Drupal 6, the best place to start is with the official theming handbook:http://drupal.org/theme-guide.

The CSS file

Next, we need to make a quick addition to the CSS filethe frobnitz.css file mentioned in the last section. This will provide a very simple style for the tool we are going to add. Here is what it looks like:

#printer-button {
float: right;
font-weight: bold;
padding-right: 20px;
}

This CSS simply adds a definition for some element with the ID of printer-button. We will see that element a little later in this chapter. The styles here will float that element to the right side of the screen, add a little padding, and make the font bold.

Adding JavaScript to a theme

We now have a shiny new theme to work with. Let's turn our attention to incorporating our JavaScript print tool into that theme.

This will require three short steps:

  1. Add a template that will display a Print link.

  2. Make a minor adjustment to the stylesheet to make this link stand out.

  3. Add the JavaScript to our page.

Let's start with the template.

Overriding a template

Bluemarine already has all of the templates required for displaying Drupal. However, we want to add a link on the righthand side of the main content display that will show a Print link. When clicked, this link will run our JavaScript.

To do this, we want to override Bluemarine's page.tpl.php file. In other words, we want to provide a Frobnitz template that will be used instead of Bluemarine's. Since we want it to primarily look the same, we will start by copying themes/bluemarine/page.tpl.php into sites/all/themes/frobnitz/.

Note

The page template (page.tpl.php) is responsible for rendering the main structure of a Drupal page, including the main body of the HTML.

Let's take a quick look at the page template just to see howit's structured. Don't worry about the details. Most of it is just boilerplate HTML:

<?php
// $Id: page.tpl.php,v 1.28 2008/01/24 09:42:52 goba Exp $
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
lang="<?php print $language->language ?>"
xml:lang="<?php print $language->language ?>"
dir="<?php print $language->dir ?>">
<head>
<title><?php print $head_title ?></title>
<?php print $head ?>
<?php print $styles ?>
<?php print $scripts ?>
<script type="text/javascript"><?php /* Needed to avoid Flash of Unstyle Content in IE */ ?> </script>
</head>
5<body>
<table border="0" cellpadding="0" cellspacing="0" id="header">
<tr>
<td id="logo">
<?php if ($logo) {
?><a href="<?php print $front_page ?>"
title="<?php print t('Home') ?>"><img
src="<?php print $logo ?>"
alt="<?php print t('Home') ?>" /></a><?php } ?>
<?php if ($site_name) { ?><h1 class='site-name'><a
href="<?php print $front_page ?>"
title="<?php print t('Home') ?>"><?php
print $site_name ?></a></h1><?php } ?>
<?php if ($site_slogan) { ?><div class='site-slogan'>
<?php print $site_slogan ?></div><?php } ?>
</td>
<td id="menu">
<?php if (isset($secondary_links)) { ?><?php print
theme('links', $secondary_links,
array('class' => 'links', 'id' => 'subnavlist'))
?><?php } ?>
<?php if (isset($primary_links)) { ?><?php
print theme('links', $primary_links,
array('class' => 'links', 'id' => 'navlist'))
?><?php } ?>
<?php print $search_box ?>
</td>
</tr>
<tr>
<td colspan="2"><div><?php print $header ?></div></td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" id="content">
<tr>
<?php if ($left) { ?><td id="sidebar-left">
<?php print $left ?>
</td><?php } ?>
<td valign="top">
<?php if ($mission) { ?><div id="mission"><?php
print $mission ?></div><?php } ?>
<div id="main">
<?php print $breadcrumb ?>
<h1 class="title"><?php print $title ?></h1>
<div class="tabs"><?php print $tabs ?></div>
<?php if ($show_messages) { print $messages; } ?>
<?php print $help ?>
<?php print $content; ?>
<?php print $feed_icons; ?>
</div>
</td>
<?php if ($right) { ?><td id="sidebar-right">
<?php print $right ?>
</td><?php } ?>
</tr>
</table>
<div id="footer">
<?php print $footer_message ?>
<?php print $footer ?>
</div>
<?php print $closure ?>
</body>
</html>

There are a few important things to note about the template.

First, templates are the only place in Drupal where you will see this mix of PHP code and HTML. By design, Drupal keeps programming logic separate from layout. Themes are the only area where these two converge.

PHP logic will always be enclosed inside the PHP processor instruction tag:<?php ... ?>

Second, the PHP code in templates is generally restricted to the following:

  • Simple print statements (print $variable_name)

  • A handful of function calls, usually to either the theming subsystem (theme()) or to the translatation subsystem (t())

  • Control structures, like if/else and foreach, to determine what needs to be displayed

If you are not a PHP expert, you can learn these techniques just by reading the themes.

Finally, the page template creates the basic framework for the page. Smaller sections are created by other templates, and provided to this template late in the rendering process.

For example, in Bluemarine, blocks are themed with block.tpl.php. Then the themed blocks are put in their designated regions. In this example, all of the blocks that are displayed in the lefthand column will be formatted and placed in the siderbar-left region, which is designated in the page.tpl.php file by the $left variable. Then the page.tpl.php file simply prints $left:

<table border="0" cellpadding="0" cellspacing="0" id="content">
<tr>
<?php if ($left) { ?><td id="sidebar-left">
<?php print $left ?>
</td><?php } ?>

<td valign="top">
<?php if ($mission) { ?><div id="mission"><?php
print $mission ?></div><?php } ?>
<div id="main">

There are many variables to keep track of in the page.tpl.php template (block.tpl.php is much simpler). Fortunately for us, we won't be dealing with all of the variables directly. We're more interested in the HTML and JavaScript.

Note

The Garland theme (themes/garland/) is very well-documented. You can get an idea of what each variable stands for by reading the comments at the top of Garland's templates.

What we now want to do is add a snippet of HTML to this theme that will add our new link. Here is our addition:

<table border="0" cellpadding="0" cellspacing="0" id="content">
<tr>
<?php if ($left) { ?><td id="sidebar-left">
<?php print $left ?>
</td><?php } ?>
<td valign="top">
<?php if ($mission) { ?><div id="mission"><?php print $mission
?></div><?php } ?>
<!-- New Content -->
<div id="printer-button"><a
href="javascript:PrinterTool.print('main')" >
Print</a></div>
<!-- End new content -->

<div id="main">
<?php print $breadcrumb ?>
<h1 class="title"><?php print $title ?></h1>
<div class="tabs"><?php print $tabs ?></div>
<?php if ($show_messages) { print $messages; } ?>
<?php print $help ?>
<?php print $content; ?>
<?php print $feed_icons; ?>
</div>
</td>
<?php if ($right) { ?><td id="sidebar-right">
<?php print $right ?>
</td><?php } ?>
</tr>
</table>

This section of code occurs about thirty five lines into page.tpl.php. The highlighted lines are highlighted are the only ones we've added. In these lines we create a new<div></div> tag, and then put a Print link inside. When clicked, this link executes the JavaScript function PrinterTool.print('main').

Recall that PrinterTool.print() takes as an argument the HTML ID (the value of id="" in an HTML tag). Taking a glance at page.tpl.php, we can quickly see what element this ID belongs to. In fact, it's directly below the link we just added:

<div id="main">
<?php print $breadcrumb ?>
<h1 class="title"><?php print $title ?></h1>
<div class="tabs"><?php print $tabs ?></div>
<?php if ($show_messages) { print $messages; } ?>
<?php print $help ?>
<?php print $content; ?>
<?php print $feed_icons; ?>
</div>

This is the section of the template the print function will load into a new window for printing. Of course, all of the PHP calls will be replaced with HTML content.

We have one short step left before we can test out our new JavaScript-enabled theme.

Adding the script file

When we add a new CSS file, we need to inform Drupal about this by adding an entry to the .info file. Adding a JavaScript file is done in the same way.

The first step in adding our JavaScript file will require copying the script into the sites/all/themes/frobnitz/ directory, and then editing the frobnitz.info file.

; $Id$
name = Frobnitz
description = Table-based multi-column theme with JavaScript enhancements.
version = 1.0
core = 6.x
base theme = bluemarine
stylesheets[all][] = frobnitz.css
scripts[] = printer_tool.js

Again, we're making only a one-line change. We're adding printer_tool.js to the list of scripts automatically included by this theme.

Before viewing, don't forget to refresh the theme cache by either clearing it or visiting the Administer | Site building | Themes page.

Now when we visit a page on our site, it should have the Print link as seen here:

Clicking on this new link should execute the JavaScript and launch our new script, which will load a printer-friendly page into its own window. It will then launch the browser's print dialog:

We've just completed our first project. We have added JavaScript to a theme.

Summary

In this chapter we have undertaken our first project. We have created a new JavaScript library and a new theme. We also added some JavaScript functionality to the new theme.

Our task here has been fine for the purpose of illustration. But, as we shall see in the coming chapters, we can accomplish much more (often with much less code) using the JavaScript libraries included with Drupal.

In the next chapter, we will look at one library (newly added in Drupal 6) that is generating a lot of excitement both in and outside of the Drupal communityjQuery, which will be our focus in Chapter 3.