Achievements 7.x-1.4: Using #theme_wrappers

 

The Drupal Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded. The recently released 7.x-1.4 update focuses on changes that make theming easier.

One of those changes was implementing #theme_wrappers. Like many other modules, Achievements has various HTML displays that are essentially containers of things: a group of unlocked achievements, a group of categorized achievements, etc. For stronger theming, one usually wraps those in an extra <div> so that CSS folks can control the container's display. In the past, I've usually accomplished this with:

    $build['achievements'] = array(
      '#prefix' => '<div class="achievement-groups">',
      '#suffix' => '</div>',
    );

Simliar code to the above exists in Drupal core (book.module, field.module, etc.) so I never gave it a second thought. However, for a themer, the above is problematic because there's no way to tweak that HTML without dipping into a hook_page_alter() and rewriting or munging the #prefix entirely. Since I've been away from Drupal theming for so long, I never knew there was a stronger alternative by using #theme_wrappers. Not surprisingly, Drupal core uses this pattern alongside the "bad" #prefix version, so a good contender for a "Novice" patch would likely be "Replace all uses of #prefix divs with #theme_wrappers".

Like other theme functions and files, #theme_wrappers requires a hook_theme() definition:

    function achievements_theme() {
      return array(
        'achievement_groups_wrapper' => array(
          'render element'  => 'element',
        ),
      );
    }

In this case, we'll use a regular ol' theme function for the callback:

    /**
     * Default theme for the wrapper around a user's achievements page.
     *  
     * @param $variables
     *   An associative array containing:
     *   - element: A render containing the user's achievements page.
     */
    function theme_achievement_groups_wrapper($variables) {
      return '<div id="achievement-groups">' . $variables['element']['#children'] . '';
    }

And finally, we can tweak the above $build render array to use it:

    $build['achievements'] = array(
      '#theme_wrappers' => array('achievement_groups_wrapper'),
    );

In this case, the resultant HTML will be exactly the same, but now a themer can more easily override it just by defining THEMENAME_achievement_groups_wrapper(). This is a far cheaper method (mentally and performant-wise) than having to futz with hook_page_alter().

Feel free to correct me if I'm wrong, but I think it would be better to instead have your theme function use <?php render($variables['element']) ?> rather than <?php $variables['element']['#children'] ?>

This particular approach was taken from theme_dashboard().
http://api.drupal.org/api/drupal/modules--dashboard--dashboard.module/fu...

Most examples in core that I've seen take the #children approach:
http://api.drupal.org/api/drupal/includes--form.inc/function/theme_conta...

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <blockquote> <ul> <ol> <li>
  • Lines and paragraphs break automatically.