A friend of mine works on a site alongside other contractors. The other contractors needed a list of the modules on the site, but didn't have permission to access the Modules admin page. Using drush, she could have done a drush pm-list and saved the result to a text file -- but that list would be out of date as soon as a module was added or removed. She asked me if it was possible to create a view of modules, and I was a little surprised to find out it was. The end result was a useful little bugger:

Screenshot of the modules view

With the exposed filters I'm using, I can search for "sites" in the path to identify contrib and custom modules (as opposed to core modules), and limit to enabled or disabled modules. It also gives me a count of the modules that match my search.

Screenshot of the modules view, filtered by path = sites, enabled = false, type = modules

Here's how to build your own:

  • Add a new view. Instead of picking Content (the default), set it to show Module/Theme/Theme engine. Create a page in Table format. I recommend showing 50 entries per page, with a pager.
Screenshot of the initial view adding page
  • Add the fields and filters shown below.
Screenshot of the view edit page
  • As you add fields, keep in mind that there are two similarly-named fields: Module/Theme/Theme engine filename and Module/Theme/Theme engine name. The filename contains the path -- e.g. sites/all/modules/block_class/block_class.module -- while the name would only be block_class. For space constraints, I prefer to leave off the filename from the fields but use it as an exposed filter with the label "Path."
  • Note for the filename and name exposed filters, you must set the exposed filter criterion to "contains," not "equals" (the default). Otherwise you will have to type an exact match for the filter to work.
  • On the view header settings, add a Results Summary field and keep the default "Displaying @start - @end of @total" text. This will produce "Displaying 1 - 50 of 230" (or whatever is accurate based on your settings). When you update your filters, this number will also change.
  • Save the view.

On one version of this view, I added another exposed filter for the filename, this time with "Does not contain." This allowed me to search "sites" and return all modules that did not have "sites" in their path, e.g. all core modules.

I use this view as a sanity check to make sure that I don't have a lot of unnecessary contrib modules I disabled but never removed. I also find it a little more friendly than a drush command for getting a list of contrib modules from different sites to compare them (as I'll be doing soon to update the Modules list on this site).

Posted on January 11, 2014, at 4:29pm

DrupalCon was held in Portland last week. Here's my list of notes. They also link to their session pages, where a lot of the videos have been posted.

For more information, and for session videos, check out the official DrupalCon 2013 website.

  • Secrets to Awesomizing Your Editor's Back-End Drupal Experience: If I'd only gone to one session, I'd have wanted it to be this one. Mike showed a variety of modules that make an editor's job a little easier. If you want editor buy-in on your Drupal site, that's a huge deal. Presented by @mikeherchel.
  • Making Sense of the Numbers: DOs and DON'Ts of Quality Performance Testing: This session covered the terms people throw around with performance testing, who has responsibility for good site performance (spoiler: it's everyone), and straightforward dos and don'ts that help you get usable data out of your performance testing. Presented by @erikwebb and @doogiemac.
  • What's New in Drush 6: I love this session for showing me how to upload my module list to Google Docs, as a spreadsheet, with ONE COMMAND. Presented by @weitzman, @msonnabaum, @jhedstro, and @grugnog.
  • Scalable and Modular Architecture for CSS: You know that sinking feeling you get when you look at a 4,000 line CSS file and wonder how it happened? This will help you avoid that feeling. Presented by @snookca.
  • Multilingual Module Madness: What i18n Modules Do You Really Need?: There are lots of multilingual modules out there. There's no perfect way to know which ones you need (other than a lot of experience), but the aim of this session was to give you a starting point based on your project's requirements. Presented by @kristen_pol.
  • What Developers Need to Know About Dev Ops: My notes on this are more haphazard than I'd like, but this session on what developers need to know about the environment their site will live in had great information -- like sample differences between a dev box and a production box, and a list of common pitfalls when you develop without understanding your production environment. Presented by @gchaix, @ramereth, @KenLett, and @basic_.
  • Should Have Made a Left Turn at Albuquerque: Building Maps in Drupal - These are really limited notes. I normally wouldn't share them, but there were two fantastic mini-sessions: one gave a "recipe" for building great maps in Drupal, and the other showed impressive interactivity features with D3js.
  • Rules Without the UI: This was a very detailed session, with lots of code examples, showing you how to set up rules in your module's code. Presented by @dwkitchen.
Posted on May 29, 2013, at 11:55am

Views offers a neat grouping mechanism that allows you to list content with matching criteria under said criterion. For example, if you had 3 press releases from 2011 and 2 from 2010, you could display them like this:

2011

  • Press Release #5 (April 1, 2011)
  • Press Release #4 (March 31, 2011)
  • Press Release #3 (January 25, 2011)

2010

  • Press Release #2 (December 3, 2010)
  • Press Release #1 (June 1, 2010)

To do this, you create a view with three fields:

  • Title (linked to node)
  • Date (in Month Day, Year format)
  • Date (in Year format; exclude this from display) -- if you want to save yourself some confusion later, open this field, go to the "More" section, and enter "Year (grouping header)" to make it clear why this field is there even though it's not shown

Next to your view format (unformatted list, HTML list, etc.), click Settings, and then pick your second date field as the grouping header under "Grouping field Nr.1". Check off "Use rendered output to group rows." Save.

I've done this quite a few times, but on my current site, I found that even when I told it to group by year, it was coming out like this:

2011

  • Press Release #5 (April 1, 2011)

2011

  • Press Release #4 (March 31, 2011)

2011

  • Press Release #3 (January 25, 2011)

2010

  • Press Release #2 (December 3, 2010)

2010

  • Press Release #1 (June 1, 2010)

This makes a little sense, since the dates all technically are different, but it's supposed to be grouping by the rendered output, which is just 2011 and 2010. I dug around and found that even though it looked like just "2011" to the naked eye, the code was actually <span class="date date-display-year" property="dc:date" datatype="xsd:dateTime" content="2011-04-01T05:00:00-05:00">2011</span>. The month, day, and even time values were still being stored even though they weren't visible.

To fix this, I opened the year field, went to the Rewrite Results section, and checked "Strip HTML tags." This gets rid of the code that differentiates the values from each other, leading to the expected grouping behavior. If you wanted to do the same thing for month and year grouping headers (e.g. December 2010), all you'd need to do is change the format of your grouping field to use the month and day instead of the year.

For more on date formats, see Modifying the page display for a monthly archive view (the "Three steps, for reusing" section).

Posted on May 14, 2013, at 11:20am

I'm working on a site that uses Conditional Fields and Taxonomy Term Reference Tree Widget. Conditional Fields allows you to show certain fields in your content type only if other fields contain a particular value, and Term Reference Tree cleans up the UI on the backend for neater nested checkbox lists.

Today I encountered two problems when using these two modules with each other. Both were odd enough that I'm writing them up to save someone else time trolling the issue queue.

Background & Examples

This client has a very complex taxonomy structure, and has a need to show some vocabularies only when certain values are provided in other vocabularies.

Here's an example of what I'm trying to do, tags renamed to protect the innocent:

field_pet has three options: cat, rabbit, dog.

field_cat_breed has three options: Persian, tabby, Manx.

field_rabbit_breed has three options: lionhead, dwarf, lop.

field_dog_breed has three options: black lab, Australian shepherd, Chow.

I only want to show field_cat_breed if field_pet is set to cat, because only cats should have a cat breed. field_rabbit_breed should only be shown if the pet is a rabbit. field_dog_breed should only be shown if it's a dog.

Unlike the examples above, the vocabularies on my site have a deep nested taxonomy, so we implemented Term Reference Tree on all the vocabularies to give them nice nested checkboxes.

Problem 1: Conditional Fields Are Always Shown

The first problem I ran into was that field_cat_breed, field_rabbit_breed, and field_dog_breed were always shown, no matter what value I picked for field_pet -- or even if field_pet was completely empty.

Problem: It appears that Conditional Fields can't process input from Term Reference Tree. I didn't find a bug report to this effect, but found that if I switched the dependee (field_pet) from Term Reference Tree to a core widget type, the problem went away.

Solution: Go to your content type > Manage Fields. Find field_pet and click edit. Click Widget Type. Change it to checkboxes/radio buttons.

Problem 2: Tags Aren't Saved on the Node

Having solved Problem #1, I set field_pet to rabbit and then set field_rabbit_breed to lionhead. I saved the node. While other tags not related to this dependency did show up (e.g. field_color), "lionhead" was stubbornly missing. I reopened the node and checked. It hadn't saved. I tried this multiple times with a variety of different tweaks, and sometimes "rabbit" did save, sometimes it didn't.

This post in the Conditional Fields issue queue gave me the answer. I had been setting my dependency like this:

Dependent: field_rabbit_breed
Dependee: field_pet
Values input mode: Insert value from widget...
[Check the box for "rabbit".]

This is much more convenient but apparently has a bug. Instead, I needed to set it manually. I checked the ID of the "rabbit" tag -- let's say 12 -- and set my dependency like this:

Dependent: field_rabbit_breed
Dependee: field_pet
Values input mode: All of these values (AND)...
Set of values: 12

I saved that dependency, added similar dependencies for field_dog_breed and field_cat_breed, went back to my node, and selected "rabbit" and "lionhead" again. It saved with no problem.

Posted on April 24, 2013, at 3:01pm

At the most recent Drupal4Gov,* Zane McHattie and I presented "Considering Accessibility Throughout the Process." This Drupal4Gov was targeted at theming and accessibility, and since I'm a front-end dev, my part of the presentation focuses on what themers can do to make sites more accessible. However, a truly accessible site is the result of developers, designers, and content creators working together.

This was my first presentation to a group of people not in my workplace, so I'll admit that I woke up very nervous the day of and wrote out crazy detailed notes on what I was going to say. Since these notes would otherwise go to waste, I repurposed them to write up this blog post.

1. Gather requirements early.

Front-end developers, designers, project managers, and clients all benefit from thinking about where time will have to be spent on accessibility during a project. If you're making a 508-compliant, accessible site, planning for this in your timelines, making sure your team knows how it will affect each member, and testing and revising throughout the process will go a long way toward a successful launch.

Kick off your project with a solid understanding of your agency's or client's requirements. While everyone is subject to Section 508, different agencies can interpret it differently and even enforce more stringent accessibility requirements (HHS is a good example). Some clients who request YouTube videos on their site may not want to spend time skinning the YouTube player to make it more accessible; others may require it.

It also helps to know what third-party content your client intends to serve up on their website (e.g. videos, podcasts, etc.) and whether the services they use for this are accessible. If you have to embed an iframe on your site with inaccessible content inside, all the work in the world won't make it compliant, and communicating that to your client early can prevent you from being held responsible for work outside your control.

Conducting some discovery on the site will help you identify places where you can compromise. Dan Mouyard of Forum One had a great presentation at Drupal4Gov on testing site accessibility; during it, he pointed out that there is no such thing as a 100% accessible site that meets every benchmark. Instead, you're aiming to get sites as accessible as possible, as fast as possible, as efficiently as possible. For example, if your client wants a maps mashup, you may choose to provide that information in an alternative format (more on that below) rather than spending a lot of time and testing on creating a map that screenreader users can browse intuitively.

Finally, remember that accessibility isn't just about screenreader users; it's about all kinds of users, from those with mobility issues to hearing impairments to those who have trouble with content that's not written in plain language. Catherine McNally's Drupal4Gov presentation did a fantastic job of showing the many types of users impacted by poor accessibility. At its heart, accessibility is good usability; everyone benefits from it.

2. Know where themers invest time making things accessible.

Assuming that you’re working from an accessible base theme that already includes basic accessibility features, such as skip navigation, you can expect to spend time on:

  • alternative formats
  • JavaScript fallbacks
  • content type adjustments
  • module markup adjustments
  • heading level adjustments
  • mobile accessibility
  • admin accessibility

This post will go over specific examples of the first five and touch on mobile and admin accessibility.

Alternative formats

During the planning phase of a project, you may identify things you want to build that will be difficult to make fully 508 compliant; knowing where you need to compromise is key here. However, any information you present on your website needs to be accessible by your full audience. You may decide to display some information in an alternative format to make sure that everyone can access it.

Screenshot of a search for Thai restaurants on Google maps

I’m showing a basic example of an alternative format above. A search on Google Maps will return a map that would be difficult for a screenreader user to navigate, with markers and even small dots that could be difficult for someone with mobility issues to click. Fortunately, there’s also a text list that shows the restaurant name, address, phone number, website, and reviews. This list can be tabbed through. That’s not to say Google does it perfectly; the lack of a focus indicator is pretty frustrating. But it gets at the heart of showing information in a way your audience can digest.

Knowing we have the option to create an alternative format, it's tempting to declare this an easy option during the planning phase; however, anything you put on your website is going to need time from a front-end developer to build and style appropriately. If you’re creating a view with a long text list of locations to complement a map, you may need to include anchor links to help users skip around more easily. You may want to include icons, as Google Maps does. Your designer will almost definitely have thoughts about how it should look. This means that even your easy option can be time-consuming, and your timeline and estimates should take this into account.

JavaScript fallbacks

If your design has interactive elements, chances are good that you’ll be using JavaScript or modules that rely on JavaScript. The simplest example is a carousel. With JavaScript enabled, it may cycle -- or allow you to move through -- several featured items. With JavaScript disabled, it still needs to allow you to access those featured items, and you probably want it to do that without breaking the look and feel of your homepage.

This screenshots below show the rotator from the Social Security Administration. With JavaScript enabled, it shows 4 slides. With JavaScript disabled, it degrades nicely to a text list. This takes theming and some forethought about what you want the non-JavaScript behavior to be.

Screenshot of SSA rotator with JavaScript on
Screenshot of SSA rotator with JavaScript off

For an example of what can happen without fallbacks, see the dropdowns in the boxes underneath the rotators. The Javascript-off version doesn't have these, meaning that someone with Javascript off won't be able to jump to any pages in that dropdown. A simple "More" link replacing the dropdown in a noscript element would make things easier.

As you’re selecting modules that use JavaScript, you might find ones that handle their own fallbacks, but this will require testing. For example, while using an earlier version of Colorbox, I used Colorbox triggers in my views assuming that they would degrade nicely to a simple link (normal Colorbox behavior). But when I tested, I ended up with links that went nowhere. This was fixed in the current version of Colorbox, but it was a valuable lesson in assumptions about module functionality.

The key here is that if you include JavaScript elements in your design, your designer or UX strategist may need to spend some time figuring out how they should work with JavaScript off and then making them work that way; at a minimum, they will have to work iteratively with the themer during development.

Content type adjustments

Whether or not your front-end developer is the one making your content types depends on your team’s workflow, but chances are good that they’ll have to touch them to make changes that affect the markup. I once worked on a project that had the alt tag in a separate field from the image due to an issue importing content with Feeds (see http://drupal.org/node/1080386).

Screenshot of image content type with alt tag in separate field

Now, if you use the alt tag attribute on the image, it will appear with its alt anywhere it’s used on the site -- most notably in views or teasers. But if you don’t, it appears with null alt. During testing, I had to make an adjustment to the content type (shown below) to add the alt field to the image and then work with the backend developer and a content person to move those alt tags.

Screenshot of image content type with alt tag as part of the image field

Alternatively, you could spend time doing views rewriting (my original approach) or making template files to get that alt tag field associated with the image; in my case, this proved too time-consuming. In short, it would be ideal if we didn’t have to revisit the content types for markup reasons, but it’s a good idea to test things out and identify trouble spots before a large-scale migration; that way you’ll save yourself some work in the long run.

Module markup adjustments

The front-end developer is probably not the person with primary responsibility for modules. But you may find yourself needing to test and override contrib module markup in one way or another -- the most common being by copying their TPLs into the theme and adjusting them. The image below shows a typical example from the Twitter Block module. It has a link wrapped around an image and some text. The link text is the person’s Twitter username, and the image gets the alt text “Twitter Avatar.”

<div class="twitter_block tweet">
  <div class="twitter_block_user">
    <a class="twitter_block profile_image" href="http://twitter.com/<?php echo $user_image; ?>">
      <img src="<?php echo $user_image; ?>" alt="Twitter Avatar" />
      <span class="twitter_block_user_name"><?php echo $user_name; ?></span>
    </a>
  </div>
  <div class="tweet_text"><p class="tweet"><?php echo $text; ?></p></div>
</div>

This alt text isn’t really useful to someone with a screenreader, and it’s repetitive for each tweet, so I copied this into my theme and replaced <img src="<?php echo $user_image; ?>" alt="Twitter Avatar" /> with <img src="<?php echo $user_image; ?>" alt="" />. The result is that all screenreaders would encounter is the username, which is much more intuitive.

This is a simple example. A more complex one is a recent update I had to make with Colorbox that didn’t include a TPL. When a Colorbox popped up, it didn’t tab the user into it, so a screenreader could open the modal window and then continue reading the page as if it hadn’t. We had relevant information in the pop-up, so we needed a way to give it focus. Since Colorbox relies on JavaScript anyway, we used JavaScript to give the first link in the pop-up focus when the trigger was clicked. Little adjustments like this are fairly simple, but they add up quickly if you’ve got a lot of contrib modules to work through.

Heading level adjustments

Your headings should be structured semantically: heading 1, then heading 2, then heading 3. Sometimes you’ll encounter modules that don’t respect this. If you use Views and group by a field, it puts a heading above a list of its children; trouble is, that’s always an h3, and sometimes an h3 isn’t appropriate -- for instance, if it’s the first heading underneath your h1, you’d want it to be an h2. Sadly, the normal way of changing HTML elements in views doesn’t work on group headers because it’s hard-coded in the TPL. However, you can hunt down that TPL (views-view-unformatted, usually; will be different if your view format isn't Unformatted) and change the h3 to an h2, globally for a view format or on an individual view basis.

<?php if (!empty($title)): ?>
  <h3><?php print $title; ?></h3>
<?php endif; ?>
<?php foreach ($rows as $id => $row): ?>
  <div <?php if ($classes_array[$id]) { print 'class="' . $classes_array[$id] .'"';  } ?>>
    <?php print $row; ?>
  </div>
<?php endforeach; ?>

To change the heading level of the grouping header, change <h3><?php print $title; ?></h3> to <h2><?php print $title; ?></h2>.

You might also use a module like Fences to pick semantically appropriate elements for different fields on your content types. For example, if you’re working on a Biography content type and you want the Role field to appear as an h2 above their responsibilities, Fences will let you pick the h2 from the Manage Fields screen. This is a great module that will save you some work as you’re creating something that’s structured semantically (and strip out a lot of unnecessary markup -- a nice plus).

Mobile and admin accessibility

Depending on the requirements of your site, there are two other areas where themers can sink a lot of time: mobile accessibility and admin accessibility. If you’re building a site that functions differently on a mobile device than on a desktop one (particularly when it involves different JavaScript), you might have to spend some time testing it in that device’s accessibility tools, like Voiceover for the iPhone.

You might also find yourself spending a lot of time on creating an accessible administrative interface, since the admins for government sites should be 508 compliant as well. It’s easy to spend time focusing only on the front-end portion of the site since that’s what the vast majority of users will see, so it bears noting up front that this is a potential trouble spot and getting your team -- or client -- on board with that.

3. Consider design accessibility, not just theme accessibility.

Plenty of people assume that a themer's going to have to spend time on this, but accessibility needs to be considered throughout the process to make sure that the end result is 508 compliant. Few designers appreciate it when a front-end developer starts changing the colors in their approved design; this means that color contrast needs to be considered up front, way before development starts. The design should be checked for color contrast before the people who’ll be approving it ever see it (see color tools at the bottom of this entry).

You’ll also want to spend some time ensuring that information is communicated with more than just color. For example, if the only difference between your links and your regular text is a dark blue color, some people may not be able to identify the links. If you add an underline, there’s an easy way to tell them apart. If your site features infographics or graphs, make sure color isn't the only way distinctions are drawn -- add patterns or, even better, clear labels.

Accessible designs go beyond color; you’ll also want to make sure that your fonts are large enough -- preferably 12pt, 10pt minimum. Your fonts should be either web-safe, or can be purchased from a service like FontSpring and embedded. This ensures that you won’t have to use images for your headings, which are worse for accessibility and much worse for your content creators to maintain. Changing a stylized image heading can become nearly impossible if you don't have the font or the original PSD.

If your designers have an opinion about how focus indicators look, they should specify them up front; otherwise the usual dotted gray box will be used when users tab through or click links. Removing the focus indicator during development should not be an option.

Finally, if your design has a lot of interactive elements that will require JavaScript, you should start thinking during the design stage about how you want the fallbacks to work and how much time you’re prepared to spend making the experience the same for users with JavaScript disabled.

4. Consider content accessibility.

If the developer receives content that isn’t accessible, the site won’t be accessible no matter how much work he or she does. Most obviously, content creators should create alt text at the time of image selection. If an image is just decorative, it’s okay to have null alt text, but if it conveys meaning -- like a photo of an agency administrator testifying before Congress -- it needs to be communicated to the user.

Both video transcripts and video captions should be provided. Contrary to popular belief, both are necessary for 508 compliance. If only video transcripts are available, make sure you budget extra time to add captioning to videos. If you're providing audio files, make sure you include an audio transcript.

Content should be structured semantically. This goes beyond just theming; content creators are going to continually interact with your WYSIWYG and will need to be trained on proper heading order (h2, then h3; no h1s, since the h1 is reserved for the page title). When possible, content should be written in plain language so it's easily understandable.

5. Test iteratively.

There are a lot of ways a site can be inaccessible, and getting a big list of deficiencies right before launch can derail a project. To avoid this, you can educate content creators early and test iteratively throughout design, development, and migration to identify problems early and correct them as they arise. The result is a more cohesive site with graceful fallbacks where they're necessary.

If someone outside your team will be evaluating the site before launch, it's a good idea to reach out to him or her early. We've had success with submitting prototypes of potentially tricky functionality for testing before heavy development begins. When you do encounter problems, these evaluators can be a resource since they often have a breadth of experience with these issues and know the quirks of their testing software enough to provide some guidance.

6. Know your tools.

There are way too many testing tools for all of them to be listed, but here are a few that we use at Rock Creek to get a jump on testing.

Color tools

Markup tools

  • WAVE (website and Firefox extension; the Firefox extension offers more thorough testing results)
  • AMP (proprietary Windows tool; there's also an express version)

Notes

Posted on February 28, 2013, at 10:04am

A site I'm currently working on sometimes hides text by text-indenting it offscreen. For example, I'm replacing the words "Toggle search" with a magnifying glass, and putting the text offscreen 99,999 pixels to the left so screenreaders can read it, but most users don't see it. The site also has a focus indicator (a dotted outline surrounding links, especially important when you're tabbing through because it shows you where you are).

In Firefox, the focus indicator wraps around both the actual link -- in this case, the toggle -- and the text that's way offscreen. The result is a huge dotted outline that's not where I want it to be at all. After hunting around for the solution, I found a blog post on accessibly hiding content that provided the "D'oh!" solution: applying "overflow: hidden" to the toggle, which has a width and height already set. The result: a focus indicator that neatly wraps around the toggle, ignoring the offscreen text.

Posted on January 25, 2013, at 9:42am

If you have your front page set to a node under "Site information" in Omega, you'll end up with two h1s on the page; one is the site name, and the other is the node title. ebeyrent's comment at drupal.org provides a snippet you can add to preprocess-page.inc to correct this:

<?php
function MYTHEME_alpha_preprocess_page(&$vars) {
  // Hide the node title on the front page
  if($vars['is_front']) {
    $vars['title_hidden'] = TRUE;    
  }
}

As always, change MYTHEME to your theme name.

For the curious, this works because of these lines in sites/all/themes/omega/omega/templates/region--content.tpl.php:

<?php if ($title_hidden): ?><div class="element-invisible"><?php endif; ?>
<h1 class="title" id="page-title"><?php print $title; ?></h1>
<?php if ($title_hidden): ?></div><?php endif; ?>

This checks for $title_hidden (which was just set in the preprocessing above) and, if it finds it, it adds a surrounding div with the element-invisible class. This does mean that the h1 still prints on the page, even if it's not seen. You could probably also unset the h1 entirely, though I haven't tried it out yet to check for adverse side effects.

Posted on January 16, 2013, at 10:22am

Themers frequently have a need to style every nth element (second, third, fourth, etc.). Sometimes, this is as straightforward as odd/even coloring on table rows; these are styling touches that can be chalked up to progressive enhancement. In the case that inspired this post, I had a layout with a three-card grid of view items, and every third item in a row needed the right margin removed so it wouldn't break to the second line. Unlike adding table striping, this is structural styling, and when it doesn't take effect because of browser restrictions (no CSS3 support, JavaScript disabled, etc.), it produces a very bad-looking page.

Views provides odd/even classes (views-row-odd, views-row-even) and number classes (views-row-1, views-row-2, views-row-3, views-row-4, and so on). Theoretically, you could apply a class to views-row-3, views-row-6, views-row-9, etc., but that would produce some truly terrible CSS and isn't worth considering when there are more elegant options.

Of those more elegant options, the three main ones are the CSS3 :nth-child pseudo-selector; the jQuery :nth-child selector; and adding a class to every third element by creating a view template. This post will touch on the advantages and disadvantages of each, along with a sample use case. While I'm writing about the third child, you can customize these for any number.

Spoiler Alert: I dug into all three options, mostly for people who have nth-child styling that's just progressive enhancement. However, if you've got a grid to deal with, I strongly recommend the grid view template approach for maximum browser compatibility.

Client-side solutions

Client-side solutions get around nth-child formatting by making changes at the browser level. If your users' browsers support them, you're golden. If they don't, they'll see your fallback. As such, these are suitable when your fallback is good enough for a decent chunk of your audience to see it.

CSS3 pseudo-selector

To select every third element with the CSS3 pseudo-selector :nth-child, you can do something like this:

.container-div .child-div:nth-child(3n+3) {
  margin-right: 0;
}

For example, if I have a view with the class blog-posts, and it contains divs with the typical class views-row, my code would look like this:

.blog-posts .views-row:nth-child(3n+3) {
  margin-right: 0;
}

Let's say you want the fifth element instead. Change the bit in parentheses: (5n+5). For more detail on the equation and what you can do with it, see CSS Tricks' How nth-child Works post.

Pros: This is quick, simple, and doesn't have the flicker that jQuery can on page load.

Cons: This has no browser support in IE8 and earlier. That means that while this is fine for progressive enhancement effects, it's no good for essential styling. In my case, removing the margin from the third element prevents a three-column grid from getting turned into a two-column grid with a weird gap on the right. Since I don't want people using IE8 to see the original problem, this isn't the right solution.

jQuery :nth-child

Although the CSS pseudo-element above doesn't have the appropriate level of browser support, there's also a similar jQuery version that you can stick in document.ready. The jQuery version of the example above would look like this:

$('.blog-posts .views-row:nth-child(3n)').addClass('third');

Note: If you're new to using jQuery in Drupal, don't forget to add the jQuery wrapper:

(function ($) {
  $(document).ready(function(event){
    $('.blog-posts .views-row:nth-child(3n)').addClass('third');
  }); // End document ready
}) (jQuery); // End custom jQuery wrapper

This would add a third class to every third element. You could then write some CSS to target this new class:

.blog-posts .third {
  margin-right: 0;
}

Pros: Browser support, assuming that JavaScript is turned on.

Cons: Can produce a flicker when the page loads where the undesirable effect is shown first and quickly replaced with the desired effect. In my case, this would be a broken grid followed by a corrected grid. Again, not great, since the difference is so noticeable. Also, it doesn't work if the user has JavaScript off.

Note: If you just want to style every other element, take advantage of jQuery's odd/even support. Here's an example where it's applied to table rows for zebra striping. This produces an "odd" class and an "even" class on each row.

$("tbody > tr:odd").addClass("odd");
$("tbody > tr:not(.odd)").addClass("even");

Server-side solutions

Grid view templates

Client-side solutions like CSS3 and jQuery are easy to apply, but less powerful than server-side solutions like templates. Rather than adding the class on the fly, templates add the class before the page is rendered, so they're friendly even to people whose browsers don't support CSS3 or have JavaScript disabled. As long as they don't have CSS itself disabled, you'll be fine.

To add the classes we need, we could make a template for each individual view (more on that below); since grids are common on my site, I prefer to style a view format I can then turn on whenever I need it. Fortunately, Views provides a default grid format with a template called views-view-grid.tpl.php. It looks like this:

<?php if (!empty($title)) : ?>
  <h3><?php print $title; ?></h3>
<?php endif; ?>
<table class="<?php print $class; ?>"<?php print $attributes; ?>>
  <tbody>
    <?php foreach ($rows as $row_number => $columns): ?>
      <tr <?php if ($row_classes[$row_number]) { print 'class="' . $row_classes[$row_number] .'"';  } ?>>
        <?php foreach ($columns as $column_number => $item): ?>
          <td <?php if ($column_classes[$row_number][$column_number]) { print 'class="' . $column_classes[$row_number][$column_number] .'"';  } ?>>
            <?php print $item; ?>
          </td>
        <?php endforeach; ?>
      </tr>
    <?php endforeach; ?>
  </tbody>
</table>

As you probably noticed, this is table-based, and table-based layouts are relics of an ancient time. But it does some very handy things, like adding a class for each column and row. In my case, it provides a nice col-3 class that I can use to style the third item in each row.

Modifying this template to be div-based rather than table-based was easy enough. I copied it to my theme, and adjusted it as follows:

  1. I removed the opening and closing <tbody> tags.
  2. I replaced the opening and closing <tr> tags with <div> tags.
  3. I replaced the opening and closing <td> tags with <div> tags.
  4. Because I was floating the elements inside the rows, I added a "clearfix" class to each row.

Here's the result:

<?php if (!empty($title)) : ?>
  <h3><?php print $title; ?></h3>
<?php endif; ?>
<div class="<?php print $class; ?>"<?php print $attributes; ?>>
    <?php foreach ($rows as $row_number => $columns): ?>
      <div <?php if ($row_classes[$row_number]) { print 'class="' . $row_classes[$row_number] .' clearfix"';  } ?>>
        <?php foreach ($columns as $column_number => $item): ?>
          <div <?php if ($column_classes[$row_number][$column_number]) { print 'class="views-row ' . $column_classes[$row_number][$column_number] .'"';  } ?>>
            <?php print $item; ?>
          </div>
        <?php endforeach; ?>
      </div>
    <?php endforeach; ?>
</div>

And here's some sample output. The contents of anything in brackets -- view classes, content, etc. -- would be specific to your view.

<div class="[view classes]">
  <div class="view-content">
    <div class="views-view-grid cols-3">
      <div class="row-1 clearfix">
        <div class="views-row col-1">[content]</div>
        <div class="views-row col-2">[content]</div>
        <div class="views-row col-3">[content]</div>
      </div>
      <div class="row-2 clearfix">
        <div class="views-row col-1">[content]</div>
        <div class="views-row col-2">[content]</div>
        <div class="views-row col-3">[content]</div>
      </div>
      <div class="row-3 clearfix">
        <div class="views-row col-1">[content]</div>
        <div class="views-row col-2">[content]</div>
        <div class="views-row col-3">[content]</div>
      </div>
    </div>
  </div>
</div>

I can now target the third item in every row with .col-3, solving my original problem nicely:

.blog-posts .col-3 {
  margin-right: 0;
}

If I want to use this same grid format on another view, I can easily apply it by using the grid format, as shown below:

Screenshot showing the placement of the grid format option

If I have 4 columns rather than 3, I can change it under the grid settings. This is the "Settings" link next to "Grid" in the screenshot above, and it will open the following dialogue:

Screenshot showing the grid settings

Pros: Browser support everywhere, provided your CSS is supported; good control over the markup; easy to apply through the views UI once you've made the template.

Cons: Not suitable if you're styling every nth element in a view but aren't using a grid layout.

Honorable mentions (not tested)

I came across an interesting post on adding classes to every nth node in an array. I didn't try this out personally, but here's the link if you'd like to give it a go: How to add classes to every X number of node in a array (http://drupal.stackexchange.com/).

If the grid format doesn't work for your purposes, you might want to take a look at this post on Stack Exchange; again, I haven't tried this out myself, and I'm adding the link only to reduce the amount of Google-fu you have to do today.

Posted on January 15, 2013, at 10:58am

This morning someone tweeted at me about a problem with the out-of-the-box archive view in Drupal. I have a lengthy post about recreating that view, and the same problem was occurring in my recreated one. If you have a date-based contextual filter -- for example, my /posts-by-date/201301 view where 201301 is the filter value -- and you put a non-date value in that URL, like /posts-by-date/foobar, Drupal throws this error:

Warning: date_timezone_set() expects parameter 1 to be DateTime, boolean given in format_date() (line 2006 of /home/MYSITE/public_html/includes/common.inc).
Warning: date_format() expects parameter 1 to be DateTime, boolean given in format_date() (line 2016 of /home/MYSITE/public_html/includes/common.inc).

This is actually really logical. Your view has been told to look for a date by your contextual filter, and you're giving it a value that isn't a date. The person who tweeted me had it happen with old WordPress URLs, which didn't have the same structure as the ones on the new site.

Fixing this, in the out-of-the-box view or a recreated one, is straightforward:

  1. Open your contextual filter. Under "When the filter value is in the URL or a default is provided," check Specify validation criteria.
  2. On the validator dropdown that will appear, select Numeric.
  3. On the action to take dropdown, select Show "Page not found."

This makes it return a 404 on non-numeric values, which is the behavior we want. Easy enough, but a quick Google search shows that plenty of sites have this problem.

I've also added this information to the original writeup.

Posted on January 11, 2013, at 7:21am

Background: Yesterday, I posted about using PHP to wrap a span around the first word in a block title. I used a block template with the note that I'd be interested in a better method, like a preprocessing solution. I got extremely helpful comments that both pointed out how to do it with preprocessing, and showed a more efficient way to do the code itself. Since it's substantially different from my original post, I've hidden that one from the main blog views, and I'm posting the new and improved method here. If you'd like, you can view the original post and its comments, but the method below is much better.


I'm working on a theme that that needs the first word in all sidebar block titles bolded. The most obvious way to do this is with jQuery (here's a comment that shows a couple ways to do it). Doing it with PHP isn't much tougher, though, and it works regardless of whether JavaScript is enabled, with no chance of a brief flicker before the words are styled.

I'm going into some detail below to show the few lines of code work. Feel free to skip to the Result h2 if you just want the code.

The Process

If you take a quick look at block.tpl.php in your base theme (we're not going to alter this file at all; this is just for reference), you'll see a <?php print $block->subject; ?>. In template.php, you can preprocess this variable to make alterations before block.tpl.php serves it up. This is done using hook_preprocess_block(). In your function, you'll replace hook with your theme name (and MYTHEME with your actual theme name, of course):

function MYTHEME_preprocess_block(&$variables) {
}

Ordinarily, you would reference the block variables with $variables['block']. As a shortcut, you can define a shorter variable for this, so you don't have to type $variables['block']; every time.

function MYTHEME_preprocess_block(&$variables) {
  $block = $variables['block'];
}

So far we've set it up, but haven't done anything yet. First, we want to break apart the words in the title. The PHP function to use here is explode, which breaks things into their component parts; to figure out where to split up the parts, it uses a separator that you define. Since we're breaking on a word boundary, I'm going to use a space. If you replaced ' ' with ',' below, it would use commas as the dividing line (so "value1,value2,value3" would get split into "value 1" "value 2" "value 3"). If you put '|' it would look for pipes. You get the idea.

function MYTHEME_preprocess_block(&$variables) {
  $block = $variables['block'];
  $words = explode(' ', $block->subject);
}

$words now contains an array with each word from the block title. An array isn't useful to us, as you'll find if you try to store it as $block->subject (which we're going to do with the final product to get it to print in the block title; remember, $block->subject is what's called in the block.tpl.php). If you go ahead and set $block->subject to $words, you'll find "Array" printing as the title of all your blocks.

Instead, we want to use PHP's implode function to collapse this back into a string. Implode sticks a glue string in between each value of the array. Our glue will be a space; since we exploded based on where the space occurs, it makes sense to stick things back together with a space.

function MYTHEME_preprocess_block(&$variables) {
  $block = $variables['block'];
  $words = explode(' ', $block->subject);
  $block->subject = implode(' ', $words);
}

Now you'll see that your block titles are printing the way they originally did. Now that we've seen how explode and implode work, the next step is wrapping that span around the first element. Before the implosion, we can modify the first element in the array by using $words[0]:

function MYTHEME_preprocess_block(&$variables) {
  $block = $variables['block'];
  $words = explode(' ', $block->subject);
  $words[0] = '<span>' . $words[0] . '</span>';
  $block->subject = implode(' ', $words);
}

Finally, we're going to add a check to make sure that the block does have a title. If not, you can wind up with spans on blocks with no title set.

The Result

function MYTHEME_preprocess_block(&$variables) {
  $block = $variables['block'];
  if ($block->subject) {
    $words = explode(' ', $block->subject);
    $words[0] = '<span>' . $words[0] . '</span>';
    $block->subject = implode(' ', $words);
  }
}

Without touching block.tpl.php, we've changed the output, so all block titles will look like <h2 class="block-title"><span>FirstWord</span> SecondWord ThirdWord</h2> instead of <h2 class="block-title">FirstWord SecondWord ThirdWord</h2> We can target the new span with some CSS:

h2.block-title span {
  font-weight: bold;
}

You can see that the preprocessing function above is shorter and more elegant than the one I originally posted (thanks, commenters).

Bonus: Region-based Modifications

As Peter pointed out in his comment, you can take this further by taking advantage of other block variables. For example, in my use case, I only need to style the first word of block titles in the second sidebar region. I could add a span to each block title on the entire site and use CSS to target just the ones I wanted to style, like this:

.region-sidebar-second h2.block-title span {
  font-weight: bold;
}

But it'd be more efficient to adjust my function, so I only ended up with spans in the regions where I wanted them. The function below is modified to take advantage of $block->region:

function MYTHEME_preprocess_block(&$variables) {
  $block = $variables['block'];
  if ($block->subject && $block->region == "sidebar_second") {
    $words = explode(' ', $block->subject);
    $words[0] = '<span>' . $words[0] . '</span>';
    $block->subject = implode(' ', $words);
  }
}

If you want to target two different regions, it's a little tweak:

function MYTHEME_preprocess_block(&$variables) {
  $block = $variables['block'];
  if ($block->subject && ($block->region == "sidebar_second" || $block->region == "content_top")) {
    $words = explode(' ', $block->subject);
    $words[0] = '<span>' . $words[0] . '</span>';
    $block->subject = implode(' ', $words);
  }
}

Now I can remove the unnecessary .region-sidebar-second from my CSS. Voilà, a bolded first word in each block title, but only in the regions I want.

Housekeeping

If you want to keep your template.php file a little cleaner, you can move this code snippet into preprocess/preprocess-block.inc. In Omega, you need to tack alpha_ onto the function name; this may not be necessary in other themes. Here's the resulting preprocess-block.inc file.

<?php
function MYTHEME_alpha_preprocess_block(&$variables) {
  $block = $variables['block'];
  if ($block->subject && ($block->region == "sidebar_first" || $block->region == "sidebar_second")) {
    $words = explode(' ', $block->subject);
    $words[0] = '<span>' . $words[0] . '</span>';
    $block->subject = implode(' ', $words);
  }
}

Thanks go to everyone who replied to my original post, especially Peter and kscheirer. This is a neater solution with extra power, and I hope the writeup is useful for others who are trying to do the same thing.

Posted on January 10, 2013, at 7:39am

Pages