Working with JavaScript in Drupal 6: Part 1

Exclusive offer: get 50% off this eBook here
Drupal 6 JavaScript and jQuery

Drupal 6 JavaScript and jQuery — Save 50%

Putting jQuery, AJAX, and JavaScript effects into your Drupal 6 modules and themes

$23.99    $12.00
by Matt Butcher | February 2009 | AJAX Content Management Drupal Open Source

In this article by Matt Butcher, 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 article is to cover the basics on how JavaScript can be used within Drupal 6. In that regard, this article will serve as a foundation for future JavaScript development. Here are the topics that we're going to cover:

  • Serving JavaScript from Drupal
  • Creating a first script

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='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="some/script.js"></script>

In this example, src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="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.

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. 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 article we will add scripts to themes and modules. As we get started, we will begin with a theme.

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.

We will start out simply with a JavaScript-enabled theme.

Project overview: printer-friendly page content

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.

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.

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();
};

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.

    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. 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.
  • 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 in a non-anonymous function declaration (function myFunction() {}).

There are other conventions, many of which you will see here. 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.

Drupal 6 JavaScript and jQuery Putting jQuery, AJAX, and JavaScript effects into your Drupal 6 modules and themes
Published: March 2009
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

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.

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.

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.

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.

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.

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.

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.

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.

Summary

In this article, we saw how Drupal handles JavaScript. We saw the Drupal coding standards, and then we created a JavaScript tool called printer_tool.js. In the next part of this article, we will create a new theme and incorporate this tool into the theme.

Drupal 6 JavaScript and jQuery Putting jQuery, AJAX, and JavaScript effects into your Drupal 6 modules and themes
Published: March 2009
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Matt Butcher

Matt is a web developer and author. He has previously written five other books for Packt, including two others on Drupal. He is a senior developer for the New York Times Company, where he works on ConsumerSearch.com, one of the most traffic-heavy Drupal sites in the world. He is the maintainer of multiple Drupal modules and also heads QueryPath – a jQuery-like PHP library. He blogs occasionally athttp://technosophos.com.

 

Books From Packt

Joomla! Web Security
Joomla! Web Security

Learning jQuery 1.3
Learning jQuery 1.3

Drupal Multimedia
Drupal Multimedia

Building Powerful and Robust Websites with Drupal 6
Building Powerful and Robust Websites with Drupal 6

WordPress Plugin Development (Beginner's Guide)
WordPress Plugin Development (Beginner's Guide)

Your rating: None Average: 5 (2 votes)
nice article by
this article is very nice
PHP by
It's worth mentioning that Full HTML doesn't allow JavaScript in Drupal. You have to use the PHP input for your node/block which is not very obvious!

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
j
B
P
w
x
u
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software