Individual Node Theming in Drupal
This quick tutorial will demonstrate how you can set up your Drupal site to allow registered users to create custom themes which they can apply to other content items (nodes).
Feature requirements
- Allow users to customize the appearance (theme) of an individual node which they created.
- Allow users to use a single customized theme for multiple nodes.
- Restrict users to customizing only a limited set of page elements (e.g., page background, block color, etc.).
- Enable users to preview their customized themes before actually applying it to a node.
Technical requirements (Drupal 6)
- Content Construction Kit (CCK)
- Node Reference Module
- Color Picker Module
- Color Picker Widget
- Image Field Module
- Views 2 Module
- Content Templates Module
Development Steps:
Before getting started, please note that this is not a tutorial on how to set up a Drupal site or install and enable modules. Rather, this tutorial is written under the assumption that readers are already familar with these steps. So, please proceed, if not already, to install the required modules listed above under Technical Requirements.
Step 1: Set Up Content Types
Create a basic content type for Custom Themes. I personally left the "Body field label" blank to disable the use of the body field as I had no use for it. However, if you have a need for it, feel free to enable it. You could use it to allow users to add dummy text to preview with the customized theme.
At this point we are not going to add any additional fields, as we will revisit this content type and add them after identifying which page elements we want to allow registered users to customize.
For the sake of this tutorial, let's assume that we want to allow users to customize nodes for Events. So, create a content type called Events with the associated fields (e.g., date, location, etc.) and add an additional field using the node reference widget called "Custom Theme."
Here is a quick outline of how I configured the node reference field, Custom Theme:
- Widget type: select list
- Required: unchecked
- Content types that can be referenced: Custom Themes
- Advanced: Nodes that can be referenced (view)
(Note: this step is extra and not required, but I chose to use the views module to create a default view to display a user's Custom Themes in a more detailed manner. For instance, my Custom Theme was 03/01/2010.)
Step 2: Set Up Node Detail Page (Event)
Focusing on the node (event) detail page in which we are allowing users to apply a customized theme, we want to ideally create a test node (event), layout all content associated with our node, and add some test data. It will be much easier to have a visual guide as we identify regions and their html elements in which we want to allow users to customize.
The following is an example of how my event page is laid out. The areas in which users are able to customize are:
- the background (light blue area)
- content area (background, text color, link color)
- right side bar blocks (background, text color, link color)
Step 3: Add fields to Custom Themes content type
After you have identified the regions and html elements that you want to customize, you should add respective cck fields to your Custom Themes content type for users to easily interact with and control desired theme changes.
Here is a snapshot of the fields I am currently using for the Custom Themes content type.
Step 4: Create Custom Theme
Now that you have all of your Custom Theme cck fields in place, go and create a new custom theme and fill out the form according to your theme/styling requirements. I will be the first to admit that I am not an adept designer, so please forgive me for any unsightly design elements.
Here is what my Custom Theme form looks like after I have added all of my cck fields and added custom colors and background images.
Is this starting to feel like MySpace yet?
After you've completed the form, please save your changes and revisit the test node (node) that you created in Step 2, and edit the node (event) to apply the newly created Custom Theme.

Step 5: Apply Custom Theme to Content Type Body Displays
Now that we have our content types in place along with some test data we can now update the detail pages for both content types to reflect the Custom Theme. Why apply the Custom Theme to the detail page of the Custom Theme? Well, as specified within the requirements outlined above, I want users to view the Custom Theme before actually applying it to a node (event).
Using the content template module, I created two new body templates:
- node-event-body.tpl.php
- node-custom_theme-body.tpl.php)
Then, I placed them within the contemplates/ directory created within the all/ directory of my site. After creating these files, make sure that the Body columns for these content types are marked as Enabled: Disk on the Content types > Content Templates Tabbed Page (/admin/content/types/templates). This signifies that these new body templates are actually recognized and will be used for Body displays.
The goal now is to take the information associated with our custom theme node and build out some css within the body templates to alter the styling of our nodes.
Here is the code that I used for the Custom theme content type and added to the node-custom_theme-body.tpl.php file.
<style type="text/css">
.page-node fieldset {
background: none;
}
.page-node #page {
background: <?php print $node->field_page_bkgimage[0]['filepath'] ? 'url(/' . $node->field_page_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $node->field_page_bkgcolor[0]['value'] ? $node->field_page_bkgcolor[0]['value'] : null;?>
<?php print $node->field_page_bkgimage_repeat[0]['value'] ? $node->field_page_bkgimage_repeat[0]['value'] : null;?>
<?php print $node->field_page_bkgimage_x[0]['value'] ? $node->field_page_bkgimage_x[0]['value'] : null;?>
<?php print $node->field_page_bkgimage_y[0]['value'] ? $node->field_page_bkgimage_y[0]['value'] : null;?>
;
}
.page-node #content .post-box {
background: none;
border: none;
}
.page-node #content,
.page-node #content #content-inner {
background: <?php print $node->field_body_bkgimage[0]['filepath'] ? 'url(/' . $node->field_body_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $node->field_body_bkgcolor[0]['value'] ? $node->field_body_bkgcolor[0]['value'] : null;?>
<?php print $node->field_body_bkgimage_repeat[0]['value'] ? $node->field_body_bkgimage_repeat[0]['value'] : null;?>
<?php print $node->field_body_bkgimage_x[0]['value'] ? $node->field_body_bkgimage_x[0]['value'] : null;?>
<?php print $node->field_body_bkgimage_y[0]['value'] ? $node->field_body_bkgimage_y[0]['value'] : null;?>
;
<?php print $node->field_body_txtcolor[0]['value'] ? 'color:' . $node->field_body_txtcolor[0]['value'] . ';' : null;?>
}
.page-node #content a,
.page-node #content a:active,
.page-node #content a:visited,
.page-node #content a:hover {
<?php print $node->field_body_lnkcolor[0]['value'] ? 'color:' . $node->field_body_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node .sidebar .block,
.page-node .sidebar .block h2,
.page-node .sidebar .block .block-inner {
background: none;
}
.page-node .sidebar .block,
.page-node .sidebar .block h2 {
<?php print $node->field_block_txtcolor[0]['value'] ? 'color:' . $node->field_block_txtcolor[0]['value'] . ';' : null;?>
}
.page-node .sidebar .block a,
.page-node .sidebar .block a:active,
.page-node .sidebar .block a:visited,
.page-node .sidebar .block a:hover {
<?php print $node->field_block_lnkcolor[0]['value'] ? 'color:' . $node->field_block_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node .sidebar .block .block-inner {
padding-bottom: 10px;
background: <?php print $node->field_block_bkgimage[0]['filepath'] ? 'url(/' . $node->field_block_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $node->field_block_bkgcolor[0]['value'] ? $node->field_block_bkgcolor[0]['value'] : null;?>;
}
</style>
<?php print $node->body ?>
Here is the code that I used for the Event content type and added to the node-event-body.tpl.php file.
<?php
$theme_node = node_load($node->field_theme[0]['nid']);
if ($node->field_theme[0]['nid']):
?>
<style type="text/css">
.page-node.node-type-event #page {
background: <?php print $theme_node->field_page_bkgimage[0]['filepath'] ? 'url(/' . $theme_node->field_page_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $theme_node->field_page_bkgcolor[0]['value'] ? $theme_node->field_page_bkgcolor[0]['value'] : null;?>
<?php print $theme_node->field_page_bkgimage_repeat[0]['value'] ? $theme_node->field_page_bkgimage_repeat[0]['value'] : null;?>
<?php print $theme_node->field_page_bkgimage_x[0]['value'] ? $theme_node->field_page_bkgimage_x[0]['value'] : null;?>
<?php print $theme_node->field_page_bkgimage_y[0]['value'] ? $theme_node->field_page_bkgimage_y[0]['value'] : null;?>
;
}
.page-node.node-type-event #content .post-box {
background: none;
border: none;
}
.page-node.node-type-event #content,
.page-node.node-type-event #content #content-inner {
background: <?php print $theme_node->field_body_bkgimage[0]['filepath'] ? 'url(/' . $theme_node->field_body_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $theme_node->field_body_bkgcolor[0]['value'] ? $theme_node->field_body_bkgcolor[0]['value'] : null;?>
<?php print $theme_node->field_body_bkgimage_repeat[0]['value'] ? $theme_node->field_body_bkgimage_repeat[0]['value'] : null;?>
<?php print $theme_node->field_body_bkgimage_x[0]['value'] ? $theme_node->field_body_bkgimage_x[0]['value'] : null;?>
<?php print $theme_node->field_body_bkgimage_y[0]['value'] ? $theme_node->field_body_bkgimage_y[0]['value'] : null;?>
;
<?php print $theme_node->field_body_txtcolor[0]['value'] ? 'color:' . $theme_node->field_body_txtcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event #content a,
.page-node.node-type-event #content a:active,
.page-node.node-type-event #content a:visited,
.page-node.node-type-event #content a:hover {
<?php print $theme_node->field_body_lnkcolor[0]['value'] ? 'color:' . $theme_node->field_body_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event .sidebar .block,
.page-node.node-type-event .sidebar .block h2,
.page-node.node-type-event .sidebar .block .block-inner {
background: none;
}
.page-node.node-type-event .sidebar .block,
.page-node.node-type-event .sidebar .block h2 {
<?php print $theme_node->field_block_txtcolor[0]['value'] ? 'color:' . $theme_node->field_block_txtcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event .sidebar .block a,
.page-node.node-type-event .sidebar .block a:active,
.page-node.node-type-event .sidebar .block a:visited,
.page-node.node-type-event .sidebar .block a:hover {
<?php print $theme_node->field_block_lnkcolor[0]['value'] ? 'color:' . $theme_node->field_block_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event .sidebar .block .block-inner {
padding-bottom: 10px;
background: <?php print $theme_node->field_block_bkgimage[0]['filepath'] ? 'url(/' . $theme_node->field_block_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $theme_node->field_block_bkgcolor[0]['value'] ? $theme_node->field_block_bkgcolor[0]['value'] : null;?>;
}
</style>
<?php endif; ?>
.....
As you will notice, there is only a small difference between the two templates. For the events, I needed to load the node reference for the selected custom theme. The way in which we achieved this is to use the node_load ($param = array(), $revision = NULL, $reset = NULL) function (api reference) passing it the nid of the selected custom theme. If the custom theme node($theme_node) is found we will then build out the css using the data provided by the custom theme($theme_node) node.
Note: if a node is not found using the node_load() function, FALSE will then be returned.
Step 6: View Your Customized Theme
Now that you have your css being constructed according to the custom theme node, you should take a look at both nodes. For the Custom Theme node this will, again, serve as a preview page for users before they actually apply it to another node(event).
Here is my Custom Theme node after saving it and viewing it in its body display.
And here is the node(event) in its body display using the Custom Theme.
Developer Notes
- Be aware of caching your detail pages for content types using custom themes as there will be a delay in updates to the Custom Theme.
- Color picker widget module: Currently, when you leave a color picker field blank or with the default '#', you will get a form submission error stating that the value is not a valid hex value. Unfortunately this isn't practical for a custom theme, as a user may not want a color for a given html element. So, although this is taboo, if you open up the colorpicker_cck.module file, you can replace line 55 to avoid this. In other words:
Replace:
if ($item['value'] !== '#' && !preg_match('/^#(?:(?:[a-f\d]{3}){1,2})$/i', $item['value'])){
with
if ($item['value'] !== '' && $item['value'] !== '#' && !preg_match('/^#(?:(?:[a-f\d]{3}){1,2})$/i', $item['value'])) {
you should now be good to go.








