Jun 14 2013
Jun 14

The people who use any given web site are all different in terms of geography, past behavior, intent, device, time of day, the temperature of the climate they’re currently in…and the list goes on. Developing for the web is anything but easy, so, over the last few years, the Drupal community has been focusing on keeping up with the various mobile device sizes, design patterns and browser technologies, putting a lot of attention on responsive design, and doing it well. So, we’ve since been doing fairly well on adapting to various devices, but what about the aforementioned traits such as intent, past behavior and the like?

We often create personas to plan and design web sites, but the content does not embody or respond to these fictional-turned-real characters. Marketers spend a lot of time producing content that could be better-tailored to each individual. An individual’s browsing history can be cookied, if they are an anonymous user, or logged in analytics and tied to their account as an authenticated user.

Amazon.com has been a leader in this domain. Just look at all the recommendations and custom-tailored content you receive when logged in to amazon.com. The site even informs the anonymous, uncookied user when there is no browsing history to act upon, encouraging further activity on the site.

Amazon.com with no browsing history

Think about a woman in her late 60s – based purely on statistics, she might opt-out if she receives an email that contains offers for power tools and pro athlete products. Or a man who receives an email with women’s clothing. Similar consequences are in play on web sites.

Another example of a leader in context is Netflix. New videos are recommended based on your viewing habits, ratings, and even your Facebook friends’ activity on Netflix.

Screen Shot 2013-06-14 at 4.39.50 PM

The buzzword for this context is called Web Experience Management (WEM). Vendors have been developing proprietary, expensive, closed-source solutions. It is to the great benefit of the Drupal community to engage in WEM as part of What Drupal Does. There is already work being done with the WEM project that allows one to track user events as they occur throughout the site. By so doing, one can customize a user’s experience so that it is unique and relevant to the user. The WEM module also integrates with Google Analytics to further track events. Any event you track in WEM can also be sent to Google Analytics.

Events are tracked via the included  Engagement API. You can track an event via PHP on your page or during a certain hook in a custom module. The PHP would look something like this:

engagement_event_track($event_name, $event_value, $event_data, $uid);

An event can also be tracked via Javascript, like so:

Drupal.engagement.track(event_type, val, data)

An event can also be tracked via HTML, such as in an HTML email.

The WEM project is currently under active development, but already usable. It needs people to help with integration into Views, Rules, and similar Drupal architecture patterns. With that, let’s close with a few screen shots of the WEM module in action, running on on Drupal 7. One can see that UI for assigning points to events within categories is simple yet effective. User segments can be built from these categories and corresponding point totals to deliver a customized, contextual experience to your user. This effort will hopefully make WEM much less of a concept of its own, and lead to the expectations of what Drupal does as a CMS. WEM is really just a CMS, with context.

Screen Shot 2013-06-12 at 3.33.41 PM Screen Shot 2013-06-12 at 3.34.33 PM Screen Shot 2013-06-12 at 3.34.44 PM

Web Experience Management: User interface in Drupal 7

Web Experience Management: User interface in Drupal 7

Mar 15 2013
Mar 15

Rather remarkably we’ve managed to avoid the top xxx module list for Drupal 7… however to recap the presentation yesterday at ACCSVa.org here it goes….

A Drupal Roadmap with Rich Gregory – Look in to some critical dev tools like Drush and other things to get you going.

1.  Display Suite (putting Views at the top almost redundant….) – thanks to Display Suite and it’s buddy Field Group Drupal 7?s core content creation kit is a flexible dashboard delivery tool.  With a few clicks you can now turn a lengthy and unintuitive form into a dashboard – i’m seeing hope for a wordpress like content adding area.

and after

and after DS + FG

Forms before Display Suite and Field Group

Forms before Display Suite and Field Group

2. Views – it should go without saying, and now that it’s going to be a part of Drupal 8 core I’m going to leave it at that… you need views to do anything worth doing.  We’ve got a half dozen or more tutorials on views here, so dig in.

3. Context – this is your logic layout tool – pick conditions and reaction.  There are numerous modules to extend context as well – in the presentation I mentioned http://drupal.org/project/context_reaction_theme however this only has a D6 option.   You’ll probably need to use http://drupal.org/project/context_addassets to do essentially the same thing.  Also note that Mobile Tools allows you to use contexts to do dramatic theming changes based on the mobile device.

First up choose the conditions for your layout

First up choose the conditions for your layout

The choose your reactions

The choose your reactions

4.Rules: Rules allows your site to become a dynamic workflow management intranet style workhorse. The amount of flexibility here, much like Views, extends beyond the scope of a simple “short stack” review, however in essence you’re taking events that happen within the site, or custom crontab events, setting conditions and triggering actions. Coupled with modules like Views Rules the possibilities are amazing.

5. Entity reference - extending CCK (part of drupal 7 core) the up-and-coming successor to References. Allow content to reference other content, and as mentioned this allows View Relationships to create a SQL JOIN on your content – get more information about your Content Author, and many more options…this post here is particularly fun with references referencing references…

6. Honorable mention: Feeds – this is the bulk content migration tool of choice for folks like myself.  It’s intuitive and lets you harvest content from various sources and ingest it in to your content types, user tables, etc.. we have a few tutorials on feeds that may help you with some specifics – it’s a powerful tool, and coupled with tools like feeds tamper there are a lot of options.

7. Honorable mention: Flag.  Give your users buttons and they’ll push them.  Flags allow your users to have simple on/off buttons – categorize content, flag spam, etc…  they of course work with views, rules, and the rest of the gang :)

So there’s my short stack for Drupal 7 – I’m sure entities and entity forms probably belong on there, however for most basic sites I think this is a good start… heck probably need to talk wysiwyg editors too…. so many modules!  Thanks again to ACCSVA.org for the conference, Rich Gregory for the great tunes and the lift, and  Blue Cat Networks – the hat is bangin.

Aug 09 2012
Aug 09

Posted Aug 9, 2012 // 0 comments

Recently we were tasked with taking on the Thomson Reuters Olympics website which involved many intricate components including the task of developing a mobile specific theme for their android users and other non-iOS devices (iOS got a separate app). I was quite excited for this task, as it stepped away from the idea of responsive design and instead focused on a lightweight mobile specific theme and content. There is still plenty of debate whether a website should be responsive for all the devices it’s viewed on, or if a theme specified for a medium is appropriate.

Mobile vs. Responsive

In the case of Thompson Reuters Olympic website, it was determined that a mobile specific theme was a better solution than creating a responsive website. The desktop theme has a plethora of widgets in the right rail which wouldn’t translate well to the mobile theme, so the plan of action was to create a single content region with a listing style layout. Although the website is built on the Omega theme at its core, (which is mobile first/responsive), it was primarily used for its HTML5 abilities, built-in grid system, and region configuration. This proved to be a great time saver for the mobile theme with most of the legwork already completed with Omega’s default settings.


Although the ultimate goal of this theme was to address users on a mobile platform, responsive design was found not to be the correct solution. The amount of content displayed would have hindered a responsive layout and experience, so it was better to have a streamlined solution and go with a mobile theme.

The mobile theme came together quite nicely with content being placed inside of the main content region and stacked vertically. Custom mobile tagged contexts were used to place the blocks in the appropriate order in accordance to the comp, (more on this later). The custom contexts allowed us to place only the content that was useful rather than serving the user all the content and hiding it via CSS, thus avoiding increased http requests and overall load time.


Using Custom Tagged Mobile Contexts

Going back to the custom tagged mobile contexts, there were a few technical challenges which lead us to that solution. The first challenge was the obvious problem of having completely different layouts for the desktop and mobile versions of the site. Creating a separate set of contexts tagged with ‘mobile’ enabled us to place content blocks in the regions that we needed them to be in. The second and more difficult challenge was the complex and multiple layers of caching in place at various levels of the project; Acquia, Akamai and Varnish. Even with the ability of creating a different set of contexts for mobile, there was still a high chance that mobile users would be served the desktop context sets just because of the caching. Switching contexts at the drupal level was not an option because of this factor.

The final solution, by Team Architect: Tobby Hagler, was brilliant and simply elegant: Akamai was already setup to do redirects to ‘m.’ urls for mobile devices. In knowing that fact, Tobby created a patch for context that would detect the domain (in this case ‘m.’) and call the mobile tagged contexts in the DB and turn those on rather than the desktop context set. Et Voila: a mobile site with the exact content that you want. So rather than device detection, this is a domain detection scheme where we already know the domain will be device specific with ‘m.’.

Mobile web doesn’t have to be a daunting task by any means. With the proper game plan, it can actually flow together quite nicely. Unfortunately there can always be the curve balls that come into play. Even with reading countless articles on creating mobile specific and responsive websites, sometimes it comes down to the core pieces of the web that give us the biggest headaches. In this case, cache didn’t make the world go round, but proved to be a fun challenge to overcome.

Josh Cooper’s user interface development skills play a vital role in bringing great design ideas into fully functioning websites.  His specializations in HTML, CSS, Javascript plus his focus on Drupal as a platform, make Josh an ...

May 11 2012
May 11

Use Case: We want patrons to find our forms easily.  If they need help finding something they should be able to go to a search bar, type in “consultation request”  and get to our consultation request form.

The Challenge:  First off Solr doesn’t index the node/add/your-content-type-here pages by default – I’m certain there’s some workaround, however it’s not obvious (dear readers are welcome to retort).  Secondly none of the form results should be indexed by solr in the first place – we certainly don’t need our patron’s requests showing up anywhere ever.

The Hack: Form Block allows you to make content types available as blocks.  Pretty straightforward, click a few buttons, go to the context editor, and woo-ha = forms in a block = we took the pages that were already returning for the queries we were interested in and just added the form to those pages.

Here’s the VERY brief overview – 1:30 seconds or so worth http://www.youtube.com/watch?v=gzvq-t1m03A that goes over turning a content type into a block as well as adding the block to a context using the context editor that comes with the admin toolbar

Form Block settings in content type

Form Block settings in content type

Now is also a good time to mention that if you are using context’s the Admin toolbar is almost required – it really extends the UI giving you a drag and drop interface for your blocks within every context active on a given page.

Adding node form in the context editor

Adding node form in the context editor

Form Nodes in context Editor

Form Nodes in context Editor

A form in a block!

A form in a block!

Jan 25 2012
Jan 25

A glossary index is a helpful tool for our patrons to find the journals that they are looking for – building your first glossary can be a struggle so here’s a 2 minute video to walk you through the basics

Working in the arguments region in views

Once we had the block built we attached it to our view of titles using contexts.  Another way that would have been acceptable would have been Views Attach.  I wanted to use the attached views, however I wasn’t entirely certain about all possible use cases, so we just went with  a block and joined everything up using contexts.

A path, node type, and taxonomy based context

And yes, we could have added “views” conditions as well, although it is already pretty much overkilled…  in any case it’s another beautiful day with contexts & views

Jan 24 2012
Jan 24
Lullabot logo

Lullabot has trained thousands of Drupal developers & guided the development of some of the largest Drupal websites.

Jan 23 2012
Jan 23

Wow, well after about an hour of debugging we found that vocabularies that support free tagging doesn’t work. The issue queue + patch for Drupal 6 is here http://drupal.org/node/748012 – (the issue queue is for D7, however the patch at the end is D6) In our use case we went the simple route, we changed the taxonomy to be a single option…

In any case – we tested multi-select and that does work in drupal context-6.3.x-dev…  Context Variable might be a workaround as well…

Jan 10 2012
Jan 10
Lullabot logo

Lullabot has trained thousands of Drupal developers & guided the development of some of the largest Drupal websites.

Sep 22 2011
Sep 22

In this article I would like to talk about creating low bandwidth versions of websites running on Drupal 6.x.

The times of dial-up and other types of slow internet connections are (almost) over, but sometimes it's still important to give your users the option to experience a light version of your website. I will explain how to implement such functionality using the context module. This will also serve as a good example of how to write context conditions.

The idea is pretty simple; If a user clicks on a 'Low bandwidth version' link, we hide some “heavy” blocks on the site. As the context module can hide and show blocks, we only need to implement a condition and use it to switch between versions of the site.

A technical aspect of this implementation is that we can keep information about the version of the site that the user requested via cookies, $_GET variable or HTTP headers (E.g. setting a header flag based on a cookie in a load balancer). This was suggested by my colleague Fabian Franz, who I would like to thank for this great idea.

The reason why we do not simply use $_SESSION to store the context is because we would like to use Varnish(reverse proxy web server). We want to keep anonymous users served from the varnish cache and reverse proxies do not support use of the $_SESSION variable. I.e. Pages will be generated by Drupal and served by Apache/lighttpd/nginx.

Let's start by writing the condition for the context module. The context module is extensible via plugins, so first we need to tell it where to look for our plugin.

<?php
/**
* Implementation of hook_context_plugins().
*/
function context_bandwidth_context_plugins() {
 
$plugins = array();   $plugins['context_bandwidth_condition'] = array(
   
'handler' => array(
     
'path' => drupal_get_path('module', 'context_bandwidth') . '/plugins',
     
'file' => 'context_bandwidth_condition.inc',
     
'class' => 'context_bandwidth_condition',
     
'parent' => 'context_condition',
    ),
  );   return
$plugins;
}
?>

Next, we need to register our plugin so that it is a condition plugin.

<?php
/**
* Implementation of hook_context_registry().
*/
function context_bandwidth_context_registry() {
  return array(
   
'conditions' => array(
     
'context_bandwidth_condition' => array(
       
'title' => t('Bandwidth'),
       
'description' => t('Used for low bandwidth version of the site.'),
       
'plugin' => 'context_bandwidth_condition',
      ),
    ),
  );
}
?>

Next, we define when we “execute the plugin”. This is the tricky part, but the idea is that during code execution, we need to check whether the condition of our plugin is met or not. Since we need to know what site version to show as early as possible, we execute on hook_init().

<?php
/*
* Implementation of hook_init().
*/
function context_bandwidth_init() {
 
// Set context.
 
if ($plugin = context_get_plugin('condition', 'context_bandwidth_condition')) {
   
$plugin->execute('all');
  }
}
?>

Now let's look at the plugin code. This is in the file includes/context_bandwidth_condition.inc.

The tricky part is that the plugin method condition_values() should always return something, otherwise the condition won't be saved.

In the options form we let the user choose when the condition will be met; I.e. Whether the site is showing the Low Bandwidth or High Bandwidth version. This will look like the following:

The last part is execution. We check what the settings of the condition are and run $this->condition_met($context) if the condition is satisfied.

<?php
/**
* Check whether current site should be Low bandwidth version.
*/
class context_bandwidth_condition extends context_condition {
  function
condition_values() {
    return array(
       
1 => t('Active condition')
    );
  }   function
options_form($context) {
   
$defaults = $this->fetch_from_context($context, 'options');
    return array(
     
'mode' => array(
       
'#title' => t('Active for'),
       
'#type' => 'select',
       
'#options' => array(
         
'low' => t('Low Bandwidth version of the site'),
         
'high' => t('High Bandwidth version of the site'),
        ),
       
'#default_value' => isset($defaults['mode']) ? $defaults['mode'] : 'low',
      ),
    );
  }   function
execute($value) {
    if (
$this->condition_used()) {
     
// Check if current site is Low Bandwidth.
     
$condition = context_bandwidth_is_lowbw_version();       foreach ($this->get_contexts() as $context) {
       
// Get the setting whether we should react on
        // Low or High bandwidth site version.
       
$options = $this->fetch_from_context($context, 'options');
       
$mode = !empty($options['mode']) ? $options['mode'] : 'low';         if (($condition && $mode == 'low') || (!$condition && $mode == 'high')) {
         
$this->condition_met($context);
        }
      }
    }
  }
}
?>

The function context_bandwidth_is_lowbw_version() checks the current version of the site in the following way:

<?php
/**
* Get current site version.
*
* @return boolean
*   TRUE if current site is Low Bandwidth version.
*/
function context_bandwidth_is_lowbw_version() {
  return (
         (isset(
$_COOKIE["low_bandwidth"]) && $_COOKIE["low_bandwidth"] == 1)
      || (isset(
$_GET["low_bandwidth"]) && $_GET["low_bandwidth"] == 1)
      || (isset(
$_SERVER["X-Use-LowBandwidth"]) && $_SERVER["X-Use-LowBandwidth"] == 1));
}
?>

Another remaining issue that we have to solve is how to implement the “switcher.” For this we will create a block.

In order to set the cookie we will use the jQuery cookie plugin (included in http://drupal.org/project/jquery_plugin). In our code we export the settings of the cookie.

<?php
/**
* Implementation of hook_block().
*/
function context_bandwidth_block($op = 'list', $delta = 0, $edit = array()) {
  switch (
$op) {
    case
'list':
     
$blocks['context_bandwidth_link'] = array(
       
'info' => t('Bandwidth version switcher'),
      );
      return
$blocks;
      break;
    case
'view':
     
$block = array();
      switch(
$delta) {
        case
'context_bandwidth_link':
         
$site_version = context_bandwidth_is_lowbw_version();           // Init cookies settings.
         
$cookie_path = ini_get('session.cookie_path');
         
$expires = ini_get('session.cookie_lifetime');
         
$expires = (!empty($expires) && is_numeric($expires)) ? time() + (int)$expires : 0;           $settings = array(
           
'cookie_info' => array(
             
'name' => 'low_bandwidth',
             
'value' => $site_version ? 0 : 1,
             
'options' => array(
               
'path' => $cookie_path,
               
'domain' => ini_get('session.cookie_domain'),
               
'secure' => false, //(ini_get('session.cookie_secure') == '1')
             
),
             
'expires' => $expires,
            )
          );          
// Add javascripts and js settings.
         
jquery_plugin_add('cookie');
         
drupal_add_js(drupal_get_path('module', 'context_bandwidth') . '/context_bandwidth.js');
         
drupal_add_js(array('context_bandwidth' => $settings), 'setting');           $link_title = $site_version ? t('High bandwidth') : t('Low bandwidth');           // Prepare settings for the link.
         
$query = $_GET;
          unset(
$query['q']);
         
$current_path = $_GET['q'];           unset($query['low_bandwidth']);
          if (!
$site_version) {
           
$query['low_bandwidth'] = 1;
          }          
$block = array(
           
'content' => theme('context_bandwidth_block_switcher', $link_title, $current_path, $query),
          );
          break;
      }
      return
$block;
  }
}
?>

The javascript shipped with our module just sets the cookie from settings when the switcher link is clicked. The method outlined above is also a great method to retrieve any cookies set via javascript from Drupal. This is useful for example when Varnish filters out the cookies to be set like described in our recent Boosted Varnish article.

An alternative to using cookies for saving the state, is using the Persistent URL module. In that case the url would not change for the normal site, but be prefixed with for example /light/ for the low bandwidth version of the site.

You are welcome to test the code of the module and give some feedback. I have attached the code at the bottom of this post (Download). I will greatly appreciate any comments.

Thanks for reading.

AttachmentSize 2.13 KB
Aug 09 2011
Aug 09

My first ever Fuse blog post will focus on the Context module developed by the DC based Development Seed. With 29577 reported installs of the module, Context is quickly climbing the module ranks. It's already part of our base install for all sites we work on here at Fuse. 

Simply put, Context lets you determine specific reactions on a set of conditions. On every page load, it checks to see if any active contexts have conditions that have been fulfilled, and if so, it performs the reaction. To show you how it works I will give you an example of what can be achieved with Context.  In this example we want to create an active menu trail for content tagged with a specific taxonomy term. That taxonomy term will be your condition and the reaction is the desired active menu trail.  Here are the steps to take to make this work: 

1. Install Context:

As of today, the latest version of context is 7.x-3.x, which is not that different from the version 6.x-3.0. I will be working with Drupal 7 version since we're using D7 for all our new builds at Fuse. Install Context the usual way just don't forget CTools, as it is a dependent module. In Drupal 6.x environment you will also need the jQuery UI module which provides you with an admin interface for some extra features. (D7 has the jQuery included within core)

2. Add a new context:

Under Structure > Context you’ll get a list of all the contexts you've created and a search bar. You should be looking at an empty list after installing the module.

On top you can +Add or +import. Lets add a new context for now (we’ll get to importing a bit later.) Adding a new context will prompt you for Name, Tag a description. The "Tag" field will be used to group contexts on the context listing page.

3. Set your conditions:

This is where you will set the various conditions for your context. As mentioned above, conditions are checked on page load, and if the condition is met, the configured reactions are performed. Context comes built in with quite a few default conditions that will probably, for the most part, fulfill your needs. However Context is fully extendible and there are already modules out there that provide new and exciting conditions and reactions. This extendibility is discussed further at the end of this post. For now, we'll just go over the default conditions:

Context: The condition is met, if another context's conditions are met. Perfect for recycling your already set context, if there are currently active contexts that you would like to base your new context on, the context option would be perfect for it. I hardly ever duplicate the exact same condition set between two or more contexts, but there is the odd time when I like to use a context I have already set and fine tune it (ie. create another condition on top of it).
Menu: Allows you to select any number of menu items. The condition is met when any of the selected menu items belong to the current active menu trail.

Node Type: Select from a list of node types. The condition is met when viewing a node page (or using the add/edit form -- optional) of one of the selected content types. 

Taxonomy: Your condition is met if the current node being viewed is referring to a particular taxonomy term. Don't confuse this condition withTaxonomy term.

Path: Allows you to supply a list of paths. The condition is met when any of one or more paths match the supplied paths.

Site-wide Context: The condition is met at all times.

Taxonomy term: Will set the context when viewing the taxonomy term's page (not a node that is referring to that taxonomy term).

User Role: The condition is met if the current user has one of the selected role(s).

User Page: Lets you choose from a list of 'User' pages. (i.e. User profile, User account form, Registration form). Condition is met when viewing the selected pages.

Views: This option will list all active views and their specific generated pages. This allows you to trigger your context for any pages that a particular view is active on.

4. Set your reaction:

Once your conditions are set, it's time to set up your reactions. Once again, we'll just go over a few of the reactions that comes with Context built-in:

Blocks: The blocks reaction is probably my most used reaction of all. It allows you to place any block in any region when the condition is met. This provides a much more flexible way to add blocks to the page than the blocks administration page (admin/structure/block) since you can use more than just the path as the criteria for when a block should be visible or not.
 

Note: 

There is one tricky thing when using Context to place your blocks and that is the ordering of the blocks within a particular region. Within a context, it's easy to reorder the blocks within a region using the standard drupal drag and drop interface. However, If you have two

different

contexts adding blocks to the same region you will need to order them manually. Under the "+add" in the region header, click the 

 icon and the weight field will appear. Here you can assign a specific weight number to your block. The weight will be respected accross all contexts so you just need to make sure the blocks you want to appear first have lower weights than ones you want to appear after.


By drag and drop sort method vs. weight select sort method:

 

Breadcrumb: Set the breadcrumb trail to a particular menu item.

Menu: Set the Menu Active class

Theme Page: Override the section title and the section subtitle of the page. This will also override your $section_title and $section_subtitle variables within your page.tpl.php.

Theme html: Add an additional html body class to the page

5. Import / Export:

You can easily export an Context by clicking on "Export" (under Operations) on the Context listing page.

The result will be a block of text that can be copied and then imported back to another site. Just select "+Import" from the top (next to the "+Add" button) and paste the exported text. Hit save and you will have an exact copy of the context.

6. Context Editor:
 

Having the Admin menu module installed, there is the handy context editor window for testing and editing contexts. Active contexts are easily detected and can be modified on the fly by adding conditions, blocks (drag and drop) and theme variables.

7. Book keeping:
 

Usually on substantial projects the Context overview list gets messy and a bit confusing. When there are a lot of contexts it can be hard to find the one that is outputting a certain block on a certain page. To avoid this confusion I recommend a few things:
 

  • Write Descriptive Descriptions! It sounds redundant, but the better your description is, the easier it will be to figure out which context is outputting "that block" in "that region" on "that page".
  • Use Tags Wisely! Tagging can be very useful since the contexts on the context listing page get grouped by tag. If you group your contexts intuitively using tags, you'll spend less time finding your contexts and more time trying to figure out if we're ever going to get multigroups back.
     

8. Extending Context using the API

As mentioned above, Context comes with an API to extend it's functionality by adding custom conditions and reactions. An example of one of these modules is Background Images (built by Fuse's own Chris Eastwood). It provides a new reaction that can change the background image of any css-selectable element on the page. While this tutorial will not delve into how to use the API to extend context (perhaps in another tutorial down the road?), I thought it was worth mentioning in case you need a condition or reaction that isn't built-in. You know how it often goes with Drupal, if it's not built-in, there may just be a module for that! 

Apr 01 2011
Apr 01

Recently, I created my first Context plugin. The proceedure was extremely straightforward, with the exception of one step, which was not obvious to me and had me a little stumped. To the end of helping others over that and possibly other humps, I'd like to present the bare bones of a context plugin.

First, a bit of background: Context follows some hybrid OOP patterns, common to a number of Drupal modules. The business end of the module is encapsulated in a class. Typically this class would extend a base class provided by the parent module. The parent module will know about your class by your implementing an 'info' hook that, when invoked, tells the parent module all it needs to know about what class to instantiate, where that class is located, how it should be identified on administrative screens, etc. (For those interested in such things, this is a peculiar implementation of the “registry pattern”).

So the first step for creating a Context plugin is to get it identified to context, by implementing two hooks:

/**
 * Implementation of hook_context_plugins()
 */
function mymodule_context_plugins() {
  $plugins = array();      
  $plugins['mymodule_context_reaction_myreaction'] = array(
    'handler' => array(
    'path' => drupal_get_path('module', 'mymodule') .'/plugins',
    'file' => 'mymodule_context_reaction_mymodulereaction.inc',
    'class' => 'Mymodule_ContextReactionMymodulereaction',
    'parent' => 'context_reaction',
    ),
  );
  return $plugins;
}
 
/**  
 * Implementation of hook_context_registry()
 */ 
 
function mymodule_context_registry() {
  return array(
    'reactions' => array(
      'myreaction' => array(
        'title' => t('React to a context'),
        'plugin' => 'mymodule_context_reaction_mymodulereaction',
      ),
    ),
  );
}

Thus we identify our plugin to Context. The identical array structures can be used to create a condition. One only needs to change 'parent' to 'context_condition' in the first hook, and the 'reactions' array key to 'conditions' in the hook_context_registry(). Also note that the above reflects my own preferences regarding class naming. Context itself uses class naming conventions borrowed from standard function naming (lowercase, underscore delimited words).

This much can be found in Context's API.txt. But the next few pieces were tricky. First, I discovered that though I could see my plugin in the context ui, I couldn't actually attach it to any conditions. To resolve this I looked at the debug reaction, which implements all of three methods in the plugin class:

/**  
 * Output context debug information.
 */
class context_reaction_debug extends context_reaction {
  function options_form($context) {
    return array('debug' => array('#type' => 'value', '#value' => TRUE));
  }
  function options_form_submit($values) {
    return array('debug' => 1);
  }
     /**
   * Output a list of active contexts.
   */
  function execute() {
    $contexts = context_active_contexts();
    foreach ($contexts as $context) {
      if (!empty($context->reactions['debug'])) {
        if (user_access('administer site configuration') && module_exists('context_ui')) {
          $name = l($context->name, "admin/build/context/list/{$context->name}", array('query' => 'destination=' . $_GET['q']));
        }
        else {
          $name = check_plain($context->name);
        }
        drupal_set_message(t("Active context: !name", array('!name' => $name)));
      }
    }
  }
}

So, even if your plugin doesn't need any options, it still has to return something by way of a configuration form. Otherwise, it won't get saved into the context.

Now, here's the final piece, the one that had me stumped. I thought that having identified my plugin, and having assigned it to a context, it would get invoked. This was not the case. So I downloaded context_og to see how other modules got their plugins to run. The secret is to implement hook_init(). Context itself will invoke it's own plugins in it's implementation of this hook, but it won't invoke another module's plugins. So your implementation of hook init should be at least this:

/**
 * Implementation of hook_init()
 */
function mymodule_init() {
  $plugin = context_get_plugin('reaction', 'myreaction');
  if ($plugin) {
    $plugin->execute();
  }
}

Context doesn't care what methods you use to invoke your plugin. You could have called this method doitforme(). The only two methods Context seems to care about in your plugin class are those two form building methods. The grand total for a minimal context plugin is three hooks and one class.

Finally, here's the whole thing in one go:

mymodule.module

/**
 * @file
 *   Provides a context reaction
 */
 
/**
 * Implementation of hook_context_plugins()
 */ function mymodule_context_plugins() {
  $plugins = array();
     $plugins['mymodule_context_reaction_myreaction'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'mymodule') .'/plugins',
      'file' => 'mymodule_context_reaction_myreaction.inc',
      'class' => 'Mymodule_ContextReactionMyreaction',
      'parent' => 'context_reaction',
      ),
    );
  return $plugins;
}
 
/**
 * Implementation of hook_context_registry()
 */ 
function mymodule_context_registry() {
  return array(
    'reactions' => array(
      'myreaction' => array(
        'title' => t('React to a context'),
        'plugin' => 'mymodule_context_reaction_myreaction',
      ),
    ),
  );
}
 
/**
 * Implementation of hook_init()
 */
function mymodule_init() {
  $plugin = context_get_plugin('reaction', 'myreaction');
  if ($plugin) {
    $plugin->execute();
  }
}

plugins/mymodule_context_reaction_myreaction.inc

/**
 * Output context debug information.
 */ 
class context_reaction_debug extends context_reaction {
  function options_form($context) {
    return array('debug' => array('#type' => 'value', '#value' => TRUE));
  }
  function options_form_submit($values) {
    return array('debug' => 1);
  }
  /**
   * Output a list of active contexts.
   */
  function execute() {
    $contexts = context_active_contexts();
    foreach ($contexts as $context) {
      if (!empty($context->reactions['myreaction'])) {
        /* Business here */
      }
    }
  }
} 
Mar 21 2011
Tom
Mar 21

Since we all love the Context module so much, I figured it was time to integrate Context with Mobile Tools and create a special Mobile Tools plugin.

The new dev version of Mobile Tools currently allows you to create a context based on device type (mobile or desktop) or device groups.

In order to use this functionality, just create a new context and search the context for mobile devices context
Mobile Tools context

This can be very practical to configure your regions depending on the device that is accessing your site!

By creating your own device detection plugin, you can define your own device groups.

View the discussion thread.

Mar 14 2011
Mar 14

I was using the brilliant context module in a project recently. The fact that it uses ctools means it has a few characteristics reminiscent of views (and panels). One of these is the import / export functionality, and the distinction between the different types of storage for the contexts you've set up - i.e.

  • normal
  • default
  • overridden

Seeing this, I was certain there must be a way of defining contexts in code in a module, similar to the way you can define default views hook_views_api() and hook_views_default_views(). However, I really struggled to find any documentation about the correct hooks and syntax to achieve this.

Of course, one way of finding out was to use the features module to package up a context and have a look at the code it produced.

It turns out this works in a very similar way to the views module (unsurprisingly given their shared heritage). I thought I'd document it here in case other people are struggling to find some clear instructions as to how to include your own default context objects in your module.

Just like with views, you need to implement an api hook, and then the actual context_default_contexts hook which returns the exported context object(s):

/**
 * Implementation of hook_ctools_plugin_api().
 */
function mymodule_ctools_plugin_api($module, $api) {
  if ($module == "context" && $api == "context") {
    return array("version" => 3); 
  }
}

and then in mymodule.context.inc something along the lines of this:

/**
 * Implementation of hook_context_default_contexts().
 */
function mymodule_context_default_contexts() {
  $export = array();
  $context = new stdClass;
  $context->disabled = FALSE; /* Edit this to true to make a default context disabled initially */
  $context->api_version = 3;
  $context->name = 'testy';
  $context->description = 'testing context';
  $context->tag = '';
  $context->conditions = array(
    'node' => array(
      'values' => array(
        'page' => 'page',
      ), 
      'options' => array(
        'node_form' => '1',
      ),
    ),
  );
  $context->reactions = array(
    'menu' => 'admin/help',
  );  
  $context->condition_mode = 0;
 
  // Translatables
  // Included for use with string extractors like potx.
  t('testing context');
 
  $export[$context->name] = $context;
  return $export;
}

Perhaps the reason I couldn't find easy documentation for this is that it's really the ctools api doing the work - I think I'll submit an issue for the context module though to suggest at least a hint is added to the README to point developers in the right direction.
[edit]I posted a documentation issue on drupal.org[/edit]

One of the posts I did find which helped was Stella Power's interesting write up on how to use ctools to create exportables in your own module.

Nov 05 2010
Nov 05

If you're using Context module in your site, and Zen as your base theme, when you upgrade to Context 3, your site will probably broke, this can be fixed quickly by editing your template.php and add this code in your hook_blocks:

<?php
function <THEMENAME>_blocks($region, $show_blocks = NULL) {
    if (
module_exists("context")){
    
     
// Since Drupal 6 doesn't pass $show_blocks to theme_blocks, we manually call
      // theme('blocks', NULL, $show_blocks) so that this function can remember the
      // value on later calls.
     
static $render_sidebars = TRUE;
      if (!
is_null($show_blocks)) {
       
$render_sidebars = $show_blocks;
      }
    
     
// Bail if this region is disabled.
      //$disabled_regions = context_active_values('theme_regiontoggle');
      //if (!empty($disabled_regions) && in_array($region, $disabled_regions)) {
        //return '';
      //}      // If zen_blocks was called with a NULL region, its likely we were just
      // setting the $render_sidebars static variable.
     
if ($region) {
       
$output = '';        $plugin = context_get_plugin('reaction', 'block');
  
       
// Add any content assigned to this region through drupal_set_content() calls.
       
$output .= $plugin->execute($region);
  
       
$elements['#children'] = $output;
       
$elements['#region'] = $region;
  
        return
$output ? theme('region', $elements) : '';
      }
    }
    else {
        return
zen_blocks($region, $show_blocks);
    }
}
?>
Aug 19 2010
Aug 19

When we originally set out to build a set of Features to support the install profile of our Knight Drupal Initiative work, we figured we would use Context to drive the creation of these features.

Contexts within the site

And this almost worked. Almost.

The Challenge

The challenge we faced in working with features involved sorting out the way Features manages dependencies. When you're setting up a feature, and especially when you're setting up a feature based on a context, the dependencies are automatically brought in and generated for you. So, most of the things that the context relies upon are automatically made a part of your feature (and Strongarm can generally grab the rest). This is great if every single one of your contexts is a completely standalone item. But, if anything in your context contains (for example) a UI element that connects over and exposes functionality that is contained within another context, then you will have two features with overlapping dependencies, and this creates conflicts.

The Goals

At the outset, we had four main goals in setting up our install profile, and the related features used within the profile.

  • Create a site that someone could install and start using;
  • For site maintainers/non-developer admins, make features easy and intuitive to use by creating a logical set of dependencies; aka, features should contain modular sets of functionality, and be as small and as lightweight as possible;
  • Retain the ability to use features to track changes in site config over time. One of the huge benefits of Features is the ability to track changes that you make in config; using the Diff module brings more of the awesomesauce, and here at FunnyMonkey we are serious about the awesomesauce;
  • Make features that are as reusable as possible.

Some Other Things That Didn't Work

Briefly, we contemplated shipping an install profile with one enormous feature. Technically, this works, and the install would have been simple, but the maintainability of this arrangement could potentially get complicated over time. Additionally, one feature that holds everything is not particularly reusable, and it doesn't reveal a clean site architecture through a set of clearly defined and managed dependencies. So, while this would have worked, and is a viable approach in some use cases, it didn't align with our goals.

The next option we considered was to use Features to define functionality, and document how to use the Context module to control block visibility and create a coherent UI to connect the various corners of the site. This would have achieved our goal of making reusable features, but it would have required non-technical users to interact with the Context UI in order to get the most from the site. Given that one of the goals of the entire Knight Drupal Initiative is lowering the barrier to entry for newer or novice users, this approach didn't seem viable either.

Hey! You Put Your Feature In My Module!

One of the many cool things about features is that it pushes config to code; to put it another way, it creates a module from config options. Like modules, features can declare dependencies. So, with this in mind, we exported some initial features that contained the overlapping dependencies as defined above. Then, we edited the exported features so that they contained just the components we wanted them to have - this process included removing the overlaps, and maintaining dependencies on both modules and other features. This allowed us to create some base features that contain the central keys to delivering the functionality - things like content types, imagecache settings, fields, etc. Then, we created some extras features that contain - for example - various views, flags, and other mechanisms used to organize information on the site. Finally, we created UI features that contain the contexts and the reactions that display specific blocks on specific pages.

As we worked on the initial site build - and the subsequent revisions of that build prior to building out features - we also paid careful attention to tags, and to maintaining some consistency with how we tagged and organized our views and contexts. This meta-organization of the building blocks within the site helped us as we got down to organizing the build into features and an install profile.

To get a sense of how this comes together, look at the screenshot below.

The UI feature

This page is pulled together via multiple different features, and they are tied together with a final UI feature.

Using this approach, we are able to meet all of our goals as defined above. Additionally, by separating the functionality from the UI that exposes that functionality, we provide more flexibility for people to use whatever means they want to display content. On our site, we use Context to control block visibility and other display settings. However, someone else could just as easily leave our UI features turned off and use Panels, and/or Drupal's core block visibility settings.

For another example, the home page of the site ships with a slide show, and content displayed within vertical tabs (the base theme we are using for this site is Hexagon; there is some real loveliness in there, but that's a topic for another post). This homepage is generated via a UI feature; if someone wants this UI, they can turn it on. Or, people can build out swappable home page UI features that build on the underlying components, and manage these changes via the features UI.

Homepage slideshow, delivered via a UI feature

The down side of this approach, of course, is that you can't use the Features UI to build your feature, and the process of defining dependencies manually requires some quality time with Strongarm and the variables table.

variables table exportables

Ideally, we could control or manually override dependencies using the Features UI, but patching Features to provide manual overrides of dependencies is no small task, and would likely come at the expense of usability.

By treating features like the modules they are, and by setting up dependencies between them, we can create small, reusable building blocks that retain the maintainability of features created via the standard Features UI. We are getting ready to release our install profile that incorporates this method of building and maintaining features; we still have some testing to do to make sure that we haven't missed or overlooked anything. And with that said, there are likely other ways of solving this, and we would love to hear about them.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web