Feb 27 2012
Feb 27

There are only a few weeks left until DrupalCon Denver, and we’re getting pumped up at Evolving Web for the year’s largest Drupal hangout. With an estimated 4,000 participants in mile high Denver, March 19-23th promises to be a week teeming with exciting sessions, networking and skiing! Four of us will be heading down to Denver, and we hope to see you there. We have lots planned for the conference from contests to sessions to code sprints

Update: We've revised our contest, see evolvingweb.ca/drupalirl for the updated version!

DrupalCon Ticket Giveaway

We have one extra ticket for DrupalCon Denver to give away. If you're a Montreal Drupaler who's planning on attending the Drupal 8 Multilingual code sprint in Denver, give us a tweet @evolvingweb and let us know you're interested!

Photo Contest

Following on the heels of our Drupal Crossword in London, comes our Drupal Celebrity Photo Contest! Hunt down your favourite Drupaler for a photo-op, then tweet the picture with hashtag #drupalceleb for a chance to win a camera! More information and contest rules at evolvingweb.ca/drupalceleb. UPDATE: To address concerns raised about privacy, we've modified our photo contest, now called Drupal In Real Life photo challenge. For more info, see the follow-up blog post Drupalcon Contest, Take Two.

Session: Multilingual Site Building with Drupal 7

Curious about what it takes to integrate multilingual support into your website? I'm co-presenting a session on multilingual Drupal with Florian Loretan from Wunderkraut. We'll introduce you to Drupal’s multilingual architecture and help you get off the right foot when planning your next big project. Our session, Don't Get Lost in Translation: Multilingual Site Building with Drupal 7, will take place on Tuesday, March 20th at 3:45pm in Room 203.

Drupal 8 Multilingual Code Sprint

Gabor Hojsty will be leading a Drupal 8 Multilingual Initiative codesprint on the last day of DrupalCon, as well the two days following DrupalCon. There has been a strong push towards greater multilingual support in Drupal 8, and we want to help as much as possible. We'll be at the sprint, so come by and help make this initiative a success. See Gabor’s post on groups.drupal.org for more information.

Silver Sponsors

We’re excited to sponsor DrupalCon Denver at the Silver level this year! Ever since DrupalCon DC, we’ve been showing our support for the conferences by pledging sponsorship at different levels. Join us in Denver at booth 103, where we’ll be hanging out for the week. Come by, say hello and grab some of our awesome swag.

While we’re busy getting ready for DrupalCon, we hope you’ve had a chance to review the schedule and fit in your favourite sessions. Tickets are still available, so get yours now. See you in Denver!

Feb 21 2012
Feb 21

Entities in Drupal 7 simplify and centralize common logic that, in earlier versions of Drupal, was duplicated in several places, or only usable in specific modules. There are, however, many features (such as saving entities!) that did not make it into Drupal 7. The Entity API module fills in these missing gaps in the core and goes a step farther by simplifying definition of new entity types, and integration with contrib modules such as Views, Rules, and Features.

Generalized API

Core provides standardized entity loading using entity_load. There are however no functions to save, view, or delete entities! The Entity module defines entity_save, entity_view, and entity_delete functions. These new functions provide the other operations that are critical when code needs to work generally for multiple entity types. Normally you have to implement your own logic for saving an entity, and hope that you are properly invoking all the right hooks, but now you don’t have to worry about writing that yourself.

Clean Object-Oriented Design

All Drupal core entities are stdClass objects. The Entity API defines an Entity class which encapsulates the entity's properties (such as its type) into one object, and wraps many of the entity operation functions such as entity_save(), entity_label(), entity_uri(), entity_i18n_string(), etc. into methods. For example, instead of writing entity_save($entity_type, $obj), you can just write $obj->save(). You can of course extend this class when defining your own entities, which is what we’ve often chosen to do.

Another general upside to using classes in Drupal 7 is that files containing classes can be automatically loaded when they are needed, so any complex logic that you might need for your class will only need to be loaded on pages where the entity is actually used. This can be a plus for performance since it decreases the amount of code loaded on each page.

Automatic CRUD UI

No one likes building complex menu structures. Thankfully, the Entity module simplifies creation of standard UIs for managing entities. All you have to do is give it a base path for the interface and define the edit form, and from there the module can create add, edit and listing pages automatically, so you don’t have to.

Views and Rules integration

Integrating with other modules can be complicated, so the Entity module includes automatic integration with several modules, including Views and Rules. It will automatically give Views data about the structure of your entity by using the database schema or the entity properties. Similarly, because the loading and saving is done via the Entity module, it can automatically trigger events in the Rules module.

Exporting and Features integration

Exporting objects and configuration into code is a huge plus for both site deployment, and change tracking. The Entity module provides functions to export entities to JSON, and cleanly integrates with Features. To export a custom entity type, all that you need to do is use a specific entity controller, and define required exportable fields such as 'machine name' and ‘status’.

Standardized definition of properties

The same data that is used to provide integration with Views is available for any module to use. The Entity module provides the hooks hook_entity_property_info(_alter) ( see also ) for defining information about an entity type's properties. This property information includes the name along with metadata like data type, label and description, and if the value is required or needs to be sanitized.

This property data can be automatically generated by inspecting the entity's database table, or you can manually specify properties using hook_entity_property_info for more advanced behavior. Along with integration with Views, this data can then be used to automatically sanitize string data, or format numbers, or reference other tables.

Validation

Building on the Entity module's property definitions, you can specify validation callback functions to validate data for properties. Heavy reliance on the Form API validation in core and contrib means that it is often very hard to validate an entity programmatically and the only real option is to re-execute the form behind the scenes. By having metadata about properties easily available, the values can be validated properly without having to work through the Form API at all.

Conclusion

I hope this has motivated you a bit to take a look at the Entity module. Usage of the module is growing daily and you may already have it installed as a dependency for a contrib module. Several high-profile contrib modules, such as Organic Groups, Commerce, Rules, Search API and Field Collection, have the Entity module as a dependency and there are more all the time.

Feb 13 2012
Feb 13

Need a simpler UI to let administrators manage fields? We recently created a new contributed module called Simple Field. This module simplifies the UI for creating fields and adding them to content types and entities. It also provides granular permissions, so you're not stuck with a single catch-all permission for managing fields. You can see a demo of the module in action at simplefield-demo.ewdev.ca (login: demo/demo).

Why do I need Simple Field?

Imagine you're creating a Drupal site and you want to let your users create fields, but you don't want to give them the 'Manage Fields UI'. The Manage Fields UI is very powerful, but it also provides a lot of settings and configuration that can be confusing for non-technical users. Giving users permission to manage fields allows them to delete fields, and you might want more granular permissions to prevent users from deleting data on the site.

Simple Field Types

The Simple Field module includes a set of Field Types. Instead of a long list of cryptic types which can intimidate non-technical users, field types are things like 'Multiple Choice' or 'Yes/No'.


List of built-in Simple Field types

Simple Field types include a core field type, plus some field widget settings. For example, rather than choosing 'Boolean', users can choose 'Yes/No', which is a Boolean field with pre-configured options for yes or no. The user adding the Simple Field will not see the 'Options' field and won't be able to change the option values.

Of course, we can't anticipate all the Simple Field types that other sites will need, so the module is extensible and allows developers to define additional Simple Field types in code.

Creating Fields is as Easy as Pie

With pre-defined field settings, creating fields becomes much easier for users. Users just need to enter the label, whether the field is required, and help text. Some Simple Field types, such as 'Multiple Choice', have an options field, but that's it.


Creating a 'Yes or No' Simple Field

Attaching Simple Fields to Content Types

You can enable Simple Field on a per content type basis, which provides a nice UI for managing the Simple Fields for that content type. The module uses modals to keep the UI concise. You can create and attach files from the same page.


UI for attaching Simple Fields to a Customer content type

An Alternative UI for Creating Fields

The Simple Field module provides an alternative UI for creating fields, which only exposes some of the field settings to users. Other administrative roles can still be given access to all of the more advanced settings through the Manage Fields UI. The fields are stored in the database in exactly the same way, so everything like Views integration still works. All settings and weights are synchronized between the two interfaces.


Simple Fields appear in the Manage Fields UI

Granular Permissions for Fields

Permissions for the Simple Field module are very granular. You can control which roles have access to which field types and whether users can delete fields or remove fields from a particular content type or entity. For example, you can configure your site so that the users with the 'service rep' role can add Yes/No, Multiple Choice, and Short Answer simple fields to the customer content type. This way, service reps can add fields on-the-fly if they realize that collecting a particular piece of information from customers is valuable.


Simple Field Permissions

Adding Simple Fields to Entities: Fields as Content

While the Simple Field module can be used to add fields to bundles (i.e. content types) it can also be used for adding fields to entities. This opens up a ton of new possibilities. For example, you can create a survey entity and allow users to attach fields to each individual entity. Used this way, the Simple Field module is kind of like an alternative to the Webform module, allowing you to add fields to pieces of content.

Presentation at DrupalCamp NJ

Alex Dergachev presented a case study at Drupal Camp NJ on February 4, 2012 and included a demo of the Simple Field module. The case study shows our original use case for the module in the context of a Drupal project for a university.

Documentation

For information on creating new types, among other things, take a look at the documentation.

Jan 09 2012
Jan 09

Evolving Web is excited to announce the start of our Drupal training program. As an Acquia training partner, we will offer training in site building, module development, and theming. So far, we have two trainings scheduled at our office in Montreal, Hello Drupal! on January 16th and Drupal for Developers on January 27th.

Drupal for Developers

Over the past few years, we've attended countless career fairs and events for computer science and software engineering students at local universities. One thing we've noticed is that a lot of students are interested in web development, but aren't learning it at university and don't know where to start learning it on their own. We've also met lots of programmers from the IT world who want to get into web development and want a way to jump-start the learning process. While open source projects like Drupal are easy to download and start hacking away at, they are also complicated pieces of software that can be intimidating.

To help address this, we're offering a Drupal for Developers course, the first of which is scheduled for January 27. This one-day course is targeted at developers who are new to Drupal but have a programming background. It will lead students thorough Drupal's hook API, basic module development, and how to build websites 'the Drupal way' by using APIs rather than writing a lot of custom code.

By offering steep discounts to students and promoting the trainings at schools in Montreal, we're hoping to attract a crowd of up-and-coming Drupalers to our first set of trainings.

Getting Started: Hello Drupal!

Hello Drupal! is a free intro to Drupal course. The goal of the course is to introduce people to Drupal and get them excited about delving more into Drupal site building or development. The Hello Drupal course attracts students with a wide variety of backgrounds from journalists looking to create their own blog to small business owners to programmers new to web development.

Hello Drupal includes a lot of hands-on exercises that get students right into building their first Drupal website. We offered this course at DrupalCamp Montreal in 2011, and are planning to continue offering it as a way to attract a larger Drupal community in Montreal. The first is scheduled for January 16th.

Site Building and Beyond

There's always a lot of demand for site building courses to get users off the ground building a robust site architecture with content types and views. In addition to site building courses, we also intend to offer courses with an emphasis on Drupal's multilingual features. There's a huge demand for multilingual websites in Canada, particularly in Montreal and Ottawa. To give you a taste of this, we will present a webinar on Building Multilingual Websites on January 11 as part of the Acquia webinar series. If you're interested in Drupal's multilingual capabilities or are in the process of building a multilingual website, please join us.

If you’re interested in attending other Drupal courses or in scheduling a private training for your organization, we encourage you to contact us. We’re excited about increasing our suite of training curricula and reaching out new Drupal adopters in the community.

Dec 23 2011
Dec 23

After DrupalCon London, I was sitting on the banks of the Thames river sipping champagne when I remembered the time I told my friend I was joining the co-op program at Concordia. He launched into a story about his own co-op experience. One day, he had to do "pen testing".

"Ooh, you got to do penetration testing?" I asked. I find computer security interesting, so I was kind of excited. But then I remembered that my friend was a business major and that there is no way he would have been doing penetration testing.

"No, I mean... I was testing pens. My supervisor gave me a ziploc bag full of pens and told me to test each one to make sure it had ink." At that, I got a mouthful of coffee up my nose.

In May, I joined Evolving Web for a co-op work term that was to last eight months, and I am happy to say that my duties have not included testing pens, cold-calling charities to sell them snake-oil SEO packages, or doing Excel data entry.

During my time at Evolving Web, I've helped build real Drupal projects. One of them will be used by friends in the future. I had a real impact on these systems, and I've grown as an engineer by getting thrown head-first into them. Sometimes I made mistakes, but I had great mentorship from the entire team. I'm proud of the work I got done.

I was fortunate enough to travel with the company to New York, Boston, London (UK), and Toronto to Drupal Camps and Cons. At these events, I was introduced to an enthusiastic and friendly community of developers, designers, project managers, and entrepreneurs. For someone who has always had trouble "getting into open source", the warm welcome by the Drupal community meant I could gain the confidence to do real open source work. I also had the privilege to give my talk on custom fields and poutine. The chance to share my knowledge with the community was fun and rewarding.



Me on the road back from Boston with Evolving Webbers Logan and Simone



The Evolving Web team at DrupalCon London

At the code sprints in London and Montreal, I contributed a modest handful of patches to Drupal core and contrib — and I got to do it on company time. Evolving Web takes the community seriously, and I've been constantly encouraged to give back as much as I can. I'm no longer afraid to jump into issue queues and submit patches. For a novice developer, I'd recommend contributing to Drupal as a great way to get used to open source — the people are just too friendly.

As my co-op term draws to an end, and I return to the cold, unforgiving halls of academia, I'll look back on my time at Evolving Web fondly. I hope that other co-op students get a chance to experience what I've experienced, because if you've got to have a co-op job, this is the one to have. I'm happy to say I'll be coming back in the summer.

Dec 21 2011
Dec 21

Today is not your lucky day. The production server went down due to a hard disk error on the VM's host machine. You don't have a high availability setup because running additional servers 24x7 is quite costly. However, you need a new machine quickly.

In this blog post, I will tell you how we deploy brand new production machines in under 20 minutes. First, I'll talk about why it is so important to be able to deploy servers quickly.

Benefits of rapid deployment

  • Better testing environments. If you want to try Memcached out on the site, you want to reproduce the current production environment quickly. You are, after all, just trying it out, and you don't want to lose a day on it. If you can run one command and have the server running, the experiment is much more feasible during your busy work week.
  • Better development environments. If you just hired a new developer, and you want them to fix a minor bug on a site you're maintaining, they shouldn't have to waste time setting up the environment before fixing the bug. They shouldn't need to know about all the legacy baggage the site has, and they shouldn't have to pester you about how to set up every component of the site.
  • Better documentation. Instead of writing extensive documentation on each step of the deployment process, you can write a deployment script. Well-written code can rarely replace good documentation, but this is one case where relying on the code is suitable. With minimal documentation effort, you should be able to roll out of bed and deploy a new copy of your most mission-critical production sites in minutes.

Steps to Rapid deployment

Set up your Chef infrastructure

At Evolving Web, we use Chef, a configuration management tool. With Chef, you manage all your configurations in one git repository, and you write them in Ruby. This lets the Chef server build a new server or update stale configurations automatically. For example, Chef will occasionally add new public keys to our authorized_keys files as the team grows.

With Chef, your site's environment is reproducible and standardized. When you're attempting to reproduce bugs, it's good to know that your development VM is the same as the one running the production server.

Make a site-specific cookbook for launching the site

We use site-specific cookbooks to launch a specific site. This will use a rotating read-only SSH key to pull code and configuration from a git repository. Once Chef builds the site, you can sync a database from backups or the production server.

Document

Write a step by step guide that anyone can read without messing up. This takes clarity and detail. Having a reproducible process for creating the VM makes this much easier; you can document as you build a test VM with VirtualBox on your own desktop. If you find that a certain part of the process doesn't work properly, you'll notice while documenting. You can fix the Chef script and go through the process again. Since it's a short process (especially with VirtualBox templates), it's not a problem to start over.

Test the process with a beginner

Find the newest recruit on the team and send them a link to your documentation page. Tell them to set up a server in 30 minutes. Can they do it without your help? If so, you've succeeded. If not, it's back to the drawing board. Make those docs shine.

Having good documentation for this makes getting new team members up to speed much faster. Passing your deployment process down generation by generation through oral tradition is not how the professionals do it.

You're done!

Now you can rest assured that you can get that site running again with no hassle. The entire development process should be more pleasant too; you can boot up a development VM in half an hour, and make it a clean environment separate from all the experimental hacks you do on your desktop.

Resources

Dec 19 2011
Dec 19

Views provides a really great interface for displaying data on your website. However, developing with Views can be confusing at first. I’m going to explain the basics of developing with Views, and how to tell Views about your own custom tables.

I’ve also found delving into the Views source code has been really helpful to me in figuring out how Views things work, so we’re going to take a look at the guts of Views. It turns out they’re not so scary, after all!

Any examples here are for Views 3 and Drupal 7.

How does Views work?

The first important thing to understand is that for every view, Views executes a (possibly gigantic) SQL query. If you go to /admin/structure/views and click on ‘Settings’, you can turn on a setting called “Show the SQL query”. This will allow you to view the SQL query that Views has constructed for any previewed view.

Here’s an example from a simple view:

<?php
SELECT 
    node
.title AS node_title
    
node.nid AS nid
    
node.created AS node_created
FROM 
    
{nodenode
WHERE 
    
(( (node.status '1') AND (node.type IN  ('article')) ))
ORDER BY 
    node_created DESC
LIMIT 10 OFFSET 0
?>

This corresponds to the configuration:

In this example, what Views has done is:

  1. Start with a base table (node)
  2. JOIN some tables to it (node_content_revision)
  3. Add WHERE and ORDER BY clauses
  4. Display everything!

When extending Views, you can customize almost anything. In particular, you
can tell Views about a new base table (via hook_views_data(_alter)) add more
WHERE and ORDER BY clauses (filter and sort handlers) render the displayed
fields differently (field handlers)

For now, we’ll just discuss the first one -- we’ll discuss the other two in
later blog posts.

Telling Views about your database: hook_views_data

Views stores all of the information about how the Drupal database is set up (columns, relationships between tables, etc.) in a giant array called $data. You can add things to this array using hook_views_data[_alter]. This is where you tell Views about any new tables you need to display data from, and how these tables relate to each other through joins and relationships.

Views in general knows how to deal with data in tables: all the basic SQL-generating code is already there. If you just want to display or filter by your data, all you need to do is set up Views in the right way.

Let’s dive into the Views source code for an example. This is going to be a huge block of code, but it turns out to be quite approachable. If we look at node.views.inc, we see:

Diving into the Views source code

We're going to go through this step by step.

<?php /**
 * Implements hook_views_data()
 */
function node_views_data() {
  
// ----------------------------------------------------------------
  // node table -- basic table information.

  // Define the base group of this table. Fields that don't
  // have a group defined will go into this field by default.
  

$data['node']['table']['group']  = t('Content'); // Advertise this table as a possible base table
  
$data['node']['table']['base'] = array(
    
'field' => 'nid',
    
'title' => t('Content'),
    
'weight' => -10,
    
'access query tag' => 'node_access',
    
'defaults' => array(
      
'field' => 'title',
    ),
  );
// For other base tables, explain how we join
  
$data['node']['table']['join'] = array(
    
// this explains how the 'node' table (named in the line above)
    // links toward the node_revision table.
    
'node_revision' => array(
      
'handler' => 'views_join'// this is actually optional
      
'left_table' => 'node_revision'// Because this is a direct link it could be left out.
      
'left_field' => 'nid',
      
'field' => 'nid',
      
// also supported:
      // 'type' => 'INNER',
      // 'extra' => array(array('field' => 'fieldname', 'value' => 'value', 'operator' => '='))
      //   Unfortunately, you can't specify other tables here, but you can construct
      //   alternative joins in the handlers that can do that.
      // 'table' => 'the actual name of this table in the database',
     
),
  );
// ----------------------------------------------------------------
  // node table -- fields
  // title
  // This definition has more items in it than it needs to as an example.
  
$data['node']['title'] = array(
    
'title' => t('Title'), // The item it appears as on the UI,
    
'help' => t('The content title.'), // The help that appears on the UI,
     // Information for displaying a title as a field
    
'field' => array(
      
'field' => 'title'// the real field. This could be left out since it is the same.
      
'group' => t('Content'), // The group it appears in on the UI. Could be left out.
      
'handler' => 'views_handler_field_node',
      
'click sortable' => TRUE,
      
'link_to_node default' => TRUE,
     ),
    
'sort' => array(
      
'handler' => 'views_handler_sort',
    ),
    
// Information for accepting a title as a filter
    
'filter' => array(
      
'handler' => 'views_handler_filter_string',
    ),
    
'argument' => array(
      
'handler' => 'views_handler_argument_string',
    ),
  );
?>

Even without any explanation, this is fairly helpful and well-documented. If we wanted to make a new base table, we could just copy this and change a few things. See Describing Tables to Views in the Views help for an in-depth description of how all of this works.

Let’s go through this code excerpt.

All of this is defined in hook_views_data().

<?php
$data
['node']['table']['group']  = t('Content');
?>

This is the name of the group node data belongs to: every field, sort, or handler defined by node is prefixed by ‘Content: ‘.

<?php
$data
['node']['table']['base'] = array(...)
?>

This defines node as a base table. The first step when creating a View is to choose a base table: the default is ‘Content’, which corresponds to the node table. The base table is the table which every other table is joined to when creating a view (node LEFT JOIN node_content_revision LEFT JOIN user …).

<?php
$data
['node']['table']['join'] = array(...)
?>

Here the way node joins to other base tables is defined: it explains how to join the node table to the node_revision table when node_revision is the base table. Describing Tables to Views explains how to set up this array, and even has a diagram!

<?php
$data
['node']['title'] = array(...)
?>

There are lots of entries like this. Each one looks like

<?php
$data
[$tablename][$columnname] = array(...)
?>

This tells Views about a new column, which can have an associated field, sort, contextual filter (argument), filter, and relationship. $columnname can either be the name of a column in $tablename, or anything you choose. If it is not the name of a column, you’ll need to specify an actual column name with ‘real field’. For example, if we wanted to add a random sort on the ‘title’ column, we could write

<?php
$data
['node']['randomtitlesort'] = array(
  
'real field' => 'title',
  
'title' => "Random title sort",
  
'sort' => array(
    
'handler' => 'views_handler_sort_random',
  ),
);
?>

The entry

<?php
'field' => array(
      
'field' => 'title'// the real field. This could be left out since it is the same.
      
'group' => t('Content'), // The group it appears in on the UI. Could be left out.
      
'handler' => 'views_handler_field_node',
      
'click sortable' => TRUE,
      
'link_to_node default' => TRUE,
     ),
?>

adds a field to the ‘Fields’ section in Views. This one is called ‘Content: Title’. There is also a sort, a filter, and an argument (contextual filter). As you can see, at minimum each needs a handler which tells Views what to do. This entire array is available to the ‘views_handler_field_node’ handler, and the ‘click sortable’ and ‘link_to_node default’ are options which configure this handler. Each handler’s options are documented at the top of the file defining the handler.

For example, http://views.doc.logrus.com/classviews__handler__field.html says that the base views_handler_field takes the options ‘click sortable’ and ‘additional fields’. It’s important to make sure you’re looking in the right version of Views: the ‘link_to_node default’ option doesn’t exist in Views 2, for example.

Resources

When I first went to the Views project page and saw the sentence “Views 3 documentation hasn't been written yet.”, I was pretty worried. How was I supposed to write code extending Views? There’s no need to despair, though. Much of the Views 2 doxygen documentation (http://views.doc.logrus.com) still applies to Views 3, and it’s a really great site to click around in.

In particular, I’ve found the following pages really helpful to keep around

I gave a presentation on this at DrupalCamp NYC: the slides. Here's some sample code that I wrote to make the slides.

There’s also the #drupal-views IRC channel on freenode.

That’s all for now! In Part II, I’ll explain how to write your own custom Views handlers, for example to do a custom sort on some data or display a complicated field.

Dec 13 2011
Dec 13

DrupalCamp NYC 10 was one of the best Drupal camps I've ever attended. With around 400 attendees, it was definitely the biggest. Somehow, it still had the intimate feel of a camp and there were lots of opportunities for conversations and networking, both during the day and at the after party.

By scheduling ten sessions during each time slot, the organizers allowed more people the opportunity to speak, ensured that there was always something for everyone, and gave sessions an intimate, bof-like feel. We really appreciated the many high-quality sessions in the dedicated track for sysadmin and performance topics, with Nathan Goulding's Chef session and Mark Sonnabaum's XHProf session stimulating a lively discussion during the long drive back to Montreal.

Julia and I had the opportunity to present sessions at the camp:

How to tell Views about your mongooses

Julia Evans presented a Views session about displaying custom data in Drupal using Views. She'll be doing a write-up soon to go along with the presentation.

Multilingual Site Building with Drupal 7

Drupal Camp NYC 10 The camp also featured one time slot for guided conversations (aka Birds of a Feather sessions) led by expert Drupalers who had presented throughout the day. These provided a place for Q&A amongst those with common interests. Setting aside a dedicated time slot ensured that there was a high level of participation.

A huge thanks to the organizers who put so much time and energy into planning the event. You guys made it look easy and fun! We're looking forward to heading back down to the tri-state area for Drupal Camp New Jersey in February.

Nov 14 2011
Nov 14

Alex, Tavish and I had a great time in the T Dot over the weekend at DrupalCamp Toronto. The camp was well-attended, there were lots of great presentations and BoFs, and we even came back with some very hardcore Druplicon touques (hats for you non-Canadians).

Here's a recap of the four sessions we presented at the camp:

Drupal 7 Higher Education Case Study

Alex did a case study on our recent work in using Drupal 7 to build an admissions portal for a major Canadian university, emphasizing how we were able to leverage Drupal 7's new Fields API to allow non-technical administrators to easily customize the online application form.

Responsive Design and Drupal Theming

I presented my approach to implementing responsive design in Drupal, including how to handling elements like Panels, tables, forms, and images. This session expanded on a blog post about responsive design and Drupal that I wrote last month.

Multilingual Site Building in Drupal 7

I also did a presentation on building multilingual sites with Drupal. My focus was on methods for content translation, which is also covered by this blog post.

Poutine Maker: A Tasty Introduction to the Field API in Drupal 7

Tavish, our superstar intern, introduced attendees to the Drupal 7 Field API with his popular Poutine-themed presentation. The presentation covered creating custom fields, including validation, saving, and formatting field data.

A huge thanks to all the organizers and sponsors for putting on such a great event, which gave us a chance to connect with many friends old and new. We're looking forward to heading back to Toronto for the Drupal Business Summit 2011 Toronto, on December 2nd, where Alex will be co-chairing a breakout session on Drupal in Higher Education with Jennifer Hols from ImageX Media.

Nov 08 2011
Nov 08

Whether we like it or not, testing takes a huge amount of time. Often a third of each web development project is spent testing, but it's not usually something that gets a lot of attention.

SimpleTest and Selenium testing are now relatively common topics of discussion in the Drupal community, but the actual testing process is less widely discussed. Figuring out what to test for, how to track the results of testing, and techniques for testing Drupal projects are difficult problems to solve.

Whatever testing or project management methodologies are used, improving the testing process can save valuable development hours and increase the chances of finishing projects on schedule. There are lots of challenges particular to testing Drupal projects, and at DrupalCamp New Hampshire in October, I got a chance to share some of this at a session about Improving Your Drupal Testing Process.

Benefits of Testing

Besides the fact that testing a website will find bugs that need to get fixed, testing can have other positive outcomes. Testing is a great opportunity to write technical documentation and documentation for site administrators. Bug reports can lead to documentation of known issues and help unearth unforeseen requirements.

Challenges of Testing Drupal Websites

Testing Content

Most Drupal sites are full of content, and testing a site with real-world content is one of the main tasks we're faced with when testing Drupal projects. Variations in content that can cause issues to appear include:

  • Quantity of content
  • Length of individual pieces of content
  • Interlinking between content
  • Different types of media
  • Language
  • Formatting

If a website includes a content import component, doing this at the beginning of a project is a great way to ensure that real content is tested as the site's functionality is built. For user-generated content, creating a suite of realistic test content that includes a variety of what users might enter is a great investment of time at the beginning of the project.

Testing Configuration

In Drupal, configuration is functionality and the configuration settings that you're delivering need to be tested thoroughly.

Since contributed modules often introduce functionality through the admin UI that is not relevant to the project at hand, documenting which functionality has been tested is important. A good way to clarify this is to use Features as a form of documentation, including certain confiuration settings in 'configuration' features which only developers can update. This can clarify what configuration is intentional and key to the functionality of the website.

Things that are Assumed to Work

When you're writing requirements for a Drupal project, a lot of things get glossed over or left over because it's assumed that Drupal core will handle them, or that they will 'just work'. It's important to remember these during the testing process:

  • Core Drupal Features
  • Security
  • Performance
  • Accessibility
  • Browser Compatibility
  • Integration with APIs
  • Import Scripts
  • Admin UI
  • Admin Workflows
  • Mail Servers

Using an Issue Tracker to Improve Testing

My presentation included a series of tips for improving bug reporting by taking advantage of issue tracking features. Over the past few months at Evolving Web, we've been working to improve how we use Redmine to manage projects and track issues, and this plays a big role in the testing process.

Since Redmine is an open source tool, we've written some custom plugins to extend what it does and customize it to fit our needs. You can read more about our Redmine Google Docs Plugin.

Regardless of which issue tracker you use, you can improve your issue tracking process by making sure that testing results in better bug reports:

  • Review issues before they're closed or resolved
  • Assign issues to the right person
  • Link to related issues
  • Don't make duplicate issues
  • Categorize and prioritize issues
  • Integrate specifications with the issue queue
  • Use pictures (i.e. screenshots)
  • Add lots of links (to the issue, to the config settings for the issue, to related Drupal.org issues etc.)
  • Include 'Steps to reproduce', or even a script to reproduce

Another key to making effective use of your issue tracker is making sure that there's an issue for everything. This means that these changes are documented and, hopefully, tested over the course of the development process.

  • Configuration changes
  • Adding a content type
  • Adding a user role
  • Installing a module
  • Installing a new language
  • Importing content
  • Writing custom code

Methods of Testing

Different situations call for different methods of testing. Some features, like validation or e-commerce transactions, are critical and very specific. These are well-suited to specific, scripted tests that can be repeated and tested frequently. At the other end of the spectrum, usability and design features are very difficult to test in a scripted fashion. Exploratory testing, in which tests can be adjusted on the fly by the tester, are better suited for these types of features.

Automated Testing

One of the major benefits of scripted tests is that they can be automated. Tests that are automated can be run frequently. Automation can also serve as documentation in the issue queue. Using a tool like Selenium IDE, non-technical testers can record the 'steps to reproduce' when creating a bug report.

What is Selenium IDE?

Selenium IDE is a Firefox add-on that makes it easy to record tests. It records your actions in the browser and then replays them when you run each test. Tests consist of a set of command, which you can edit. You can also 'verify' that an element appears on the page in a particular place. A simple example of a Selenium test for Drupal would be filling in the fields for a particular content type and then verifying that they appear in the correct place on the node page. The Software Testing Club has a great intro to Selenium series if you're looking for help getting started.

Sharing Tests

Automated tests can be hard to maintain, since they need to be updated as the functionality of the site changes. Sharing tests through version control or on a testing server can help reduce efforts in writing automated tests. Selenium tests include a 'base URL', which lets you easily change the base URL of tests so they can be run on different environments.

Managing Test Content

One of the first things you'll notice when running Selenium tests is how quickly you can create a lot of test content. Writing tests that create, verify, and then remove content is a good way to manage this. It will also avoid overlapping namespaces if you're testing items that have to be unique, like adding custom fields to a content type or creating users.

Test Suites

Besides writing individual tests, Selenium allows you to create test suites which group tests together and allows you to run them sequentially. This is really for a set of tests for a particular user role. Rather than including a login step at the beginning of each test, this means you can login once before the tests are run.

Beyond the Basics

Selenium also has a Web Driver which can run tests server-side and allow you to test in other browsers. This is useful if you want to extend what Selenium can do, run your tests automatically, or write tests by hand.

We've come a long way since the early days of tracking issues on a white board, but our quest to turn Redmine into the ideal issue tracker is far from over. We're also still figuring out the best way to integrate unit tests and automated tests into our development workflow. As Drupal projects become more complex, testing becomes more important to each project's success. We hope to share more about the evolution of our testing process in the near future, so stay tuned!

Nov 03 2011
Nov 03

Last weekend, we headed down to Manchester for DrupalCamp New Hampshire. Building on last year's successful Drupal beginner training day, the local user group organized an awesome one-day camp with 30+ great sessions, training, and even a code sprint.

The camp had a great turnout, with over 140 hearty New Englanders (and two foolish Floridians!) braving an epic blizzard (dubbed "Snowtober") that knocked out power for much of the state.

I really enjoyed having a chance to present on Evolving Web's approach to Drupal testing and QA, which included Alex's impromptu demo of Evolving Web's custom Redmine plugins for Google Spreadsheet integration and simplified wiki editing.

Snowtober in New HampshireThe camp featured five session tracks, which meant there was always something for everyone. The after-party at a local microbrewery was a great way to reconnect with friends and catch up on Drupal gossip while drinking a locally brewed pumpkin ale and watching the snow fall. I'm very glad that the power didn't go out until midnight!

You can find photos and tweets from the camp via this Storify page.

Prep for DrupalCamp New Hampshire I'd like to thank Jake Strawn (@himerus) and Michelle Lauer (@bymiche), for organizing a great event, and for hosting me and other attendees at their place for two nights. They're simply an awesome Drupal couple.

Oct 04 2011
Oct 04

We've been hearing a lot about responsive design and the future of the web recently. At DrupalCamp Montreal this September, Jen Simmons and Jake Strawn talked about why responsive design is taking hold. I was inspired to try out some of the techniques they talked about, and decided to convert evolvingweb.ca to be more responsive.

Unlike creating an iPhone app for your website or throwing together a mobile theme, applying responsive design to your existing site requires you to change the design of your website. What I thought would be a few small adjustments to the theme turned into a larger project to make our design and theme more flexible and robust.

What is Responsive Design?

Rather than designing a site that works for one screen size, or designing separate themes that work for different devices, the goal of responsive design is to build a website that adapts to the width of the user's screen and works for both large and small screens. This involves creating a flexible-width layout that will adapt as the screen size changes, and writing some CSS specific to screens with a particular size so that you can trim down or hide certain elements at smaller widths and restrict the width of the page at large screen widths.

Media Queries

Currently, the main technique for implementing responsive design is to use media queries. Media queries allow you to include CSS files based on the size of the user's screen or device. At different screen widths (or device widths), you can load different CSS files which change the layout and styling of the page. You can do this either by targeting screens with a particular minimum width, maximum width, or both.

CSS for Larger Screens

To include CSS files for screens larger than a certain width, you can use the following in your mytheme.info file:

stylesheets[screen and (min-width: 760px)][] = css/screen-760.css
stylesheets[screen and (min-width: 960px)][] = css/screen-960.css

The screen-760.css file is loaded for browsers the width of 760px, but also larger screens. It allows for a total page width of 760px and also defines a layout for the columns to fit in that width.

The screen-960.css file is loaded for any a screen width of 960px or larger. It restricts the width of the page to 960px and allows for normal-sized column widths (i.e. 640px for the main content and 320px for the sidebar). If you're converting an existing theme to be responsive, this will contain a lot of what used to be in your layout CSS file.

CSS for Smaller Screens

Include the following in your .info file to include CSS for screens smaller than a certain width:

stylesheets[screen and (max-width: 320px)][] = css/screen-320.css
stylesheets[screen and (max-width: 480px)][] = css/screen-480.css

The screen-320.css file would only be loaded for screens 320px wide or narrower. The screen-480.css file would be loaded for screens 480px or narrower. Notice that since a device that is 480px wide will load both files, it makes sense to load the screen-480.css file second so it overrides the styles in the 320px stylesheet.

CSS for a Range of Screen Sizes

You can also effectively set a range of screen sizes that your CSS will target by combining a min-width and min-width in your media query.

stylesheets[screen and (min-width: 480px) and (max-width: 760px)][] = css/screen-480-760.css

Inline Media Queries

Depending on your design, you might end up with a lot of screen-size-specific CSS files. Sometimes it's more appropriate to use several media queries within a CSS file, using the @media rule.

@media screen and (max-width: 480px) {
  #page {
      width: 480px;
  }
}

Device Width vs. Browser Width

I should clarify that using min-width means that the CSS file will be loaded based on the browser width, so this will apply to users of large monitors who resize their browser. This is really useful for testing your responsive design, since you don't need to switch devices to see your design change, just change the width of your browser. If you only want to detect the device width only, use min-device-width or max-device-width.

It's Not Working on Android!

In addition to adding these media queries, Android devices need a 'viewport' meta tag to being able to detect their own screen width. Adding the media queries above won't have any effect on Android devices until you add this meta tag to your site. You can add it from your mytheme_preprocess_html() function using drupal_add_html_head() like this:

<?php
  $meta_viewport 
= array(
    
'#type' => 'html_tag',
    
'#tag' => 'meta',
    
'#attributes' => array(
      
'name' => 'viewport',
      
'content' => 'width=device-width'
    
)
  );
  
drupal_add_html_head($meta_viewport'viewport');
?>

What to do about Internet Explorer?

Internet Explorer versions 6-8 don't implement media queries. For these versions of IE, you can either implement some kind of responsive solution with javascript, or you can decide not provide the responsive layout for IE users, which the approach I took. In this case, you still need to provide CSS targeting IE so that users viewing your website on a larger device with IE will not see a full-width design. To implement this approach, the CSS you'll want to add for IE will likely be the same as what you added for screens that have a min-width of 960px (or whatever your widest page layout is). Drupal doesn't allow you to add a CSS file more than once, so you'll have to create a duplicate CSS file. My solution was to create a separate screen-960-ie.css file and use @import to include in it all the CSS from the screen-960.css file.

You can add a CSS file for IE only using the Conditional Stylesheets module. You can also add the file from your template.php file using drupal_add_css() and setting the browsers parameter.

Designing for Mobile

The instructions above will get you started adding media queries to your Drupal theme, but I think there's a lot to be said about how to actually create a 'mobile first' design. As themers, we're not used to designing and writing flexible-width themes for tiny devices. Even though you might like the idea of 'mobile first', your client probably expects you to design for standard-sized monitors and adapt the design for smaller devices afterwards.

Adapting a design to be responsive after it's fleshed out for a fixed-width screen is not an easy task. You can stack columns on top of each other at smaller widths, make certain elements smaller, and remove others altogether, but some elements are going to be hard to adapt. If you resize your browser to a smaller width, you'll notice a lot of awkward layouts (i.e. an image that takes up almost the full width of a column, with just a couple words wrapping to the right of it).

The best approach is to start thinking about how to make your theme responsive as you're designing and building it, and testing it at various screen widths as you go. This is going to be a challenge when the designer and themer are different people, especially if the designer is working exclusively in Illustrator or Photoshop.

Writing CSS for Responsive Design

Here are some tips to get you started writing CSS for responsive design.

  • Watch out for elements that are positioned absolutely. Will they overlap with other items at smaller screen widths?
  • Make sure your horizontal menus items wrap gracefully. Try centering them on smaller screens and using the CSS3 white-space: nowrap property.
  • Check the line-height on text that wraps at smaller screen widths.
  • Set a max-width for images of 100%, as well as for the HTML tag that wraps each image.
  • Set a max-width of 100% for form elements as well.
  • Watch out for elements with a fixed height or width. Try setting a min or max width or height instead.
  • Watch out for elements that are altered with javascript, including slideshows and text replacement tools.
  • Watch out for text that floats next to images. Do you need to change this behaviour for smaller width screens?

What do do with Tables and Panels

I found dealing with tables and Panels to be a huge challenge. Columns could be removed at smaller widths, or stacked on top of each other rather than sitting next to each other.

  • Panels layouts with more than one or two columns will not work well on smaller screens. Try writing CSS to remove columns or float columns differently at smaller widths.
  • If you're using grid-style Views to display non-tabular data, consider another display technique.
  • If you need to display data in a table, consider using advanced CSS techniques as described in this article.
  • Some of your mobile users will have an app for displaying spreadsheets on their device, so consider generating a CSV for users to download if you have a lot of tabular data to display.

Resources

Beyond Responsive Design

For some use cases when mobile performance is a priority, you'll probably want to go beyond responsive design and actually change the markup of the page for different devices. To get started doing this in Drupal, try the Mobile Tools or Context User Agent. However, mobile detection doesn't replace responsive design, and using device detection is probably out-of-scope for most Drupal projects.

Responsive design is really exciting and a great challenge for both designers and themers. While using a base theme that provides a responsive layout is great, it doesn't mean that you don't have to think about responsive design. We need to understand how different screen sizes effect our designs and how to meet the challenge of responsive design by creating more robust and more flexible themes.

Sep 30 2011
Sep 30

This September, to kick off Drupal Camp Montreal 2011, the Montreal Drupal community held our first large-scale Drupal code sprint. The sprint was held from September 14-16 at Notman House and carried on at the camp until September 18th. The sprint was spearheaded by Gábor Hojtsy who is leading the Drupal 8 Multilingual intiative. Francesco Placella, who has contributed to Drupal 7’s field translation API as well as the Entity Translation module, was also a key contributor.

In Quebec, support for multilingual websites is extremely important as most projects include some type of multilingual user interface and content. Holding a code sprint for the multilingual initiative was a perfect opportunity for our community to contribute to Drupal. Overall, there were over 15 contributors who attended the code sprint including developers, UX designers, site builders, and new-comers to Drupal. Our whole team participated in the sprint and three of our developers attended all five days of the sprint.

The code sprint covered a lot of ground, including documentation, UX improvements, and patches to Drupal core and the Entity Translation module. UX improvements were proposed for installing new languages and configuring languages in Drupal. Core development was done to improve how various Drupal APIs handle language. Since most Drupal developers don't have experience with multilingual, this is key to improving Drupal's overall multilingual support. For an overview of all the progress made, read Gábor’s summary of the sprint or watch his presentation from the camp on the Drupal 8 Multilingual initiative. Here’s a summary of our contributions:

Code

Logan Smyth worked on creating new functions for managing multilingual field items (http://drupal.org/node/1260640), adding support for Javascript versions of t and formatPlural (http://drupal.org/node/488496), moving the language domain and prefix settings to the language negotiation configuration page (http://drupal.org/node/1280530), and creating a new API for managing Locale source and target strings (http://drupal.org/node/361597).

Tavish Armstrong worked on language handling in the form API (http://drupal.org/node/1280996), the API for deleting languages (http://drupal.org/node/1260528), and compatibility between Node Clone and Entity Translation (http://drupal.org/node/1230858).

Thomas Getgood, our newest developer, worked on a patch to enable bulk field language updates when switching field translatability for the Entity Translation module (http://drupal.org/node/1279372).

Documentation

Suzanne Kennedy, our front-end developer, worked on multilingual documentation with Sylvain Aube of Whisky Echo Bravo. Together, they cleaned up the existing documentation and added pages on Entity Translation (http://drupal.org/node/1280632). They also pushed to make the Drupal multilingual documentation guide a top-level handbook on drupal.org, which it now is. You can learn more about the Entity Translation module and how it compares to Content Translation from Suzanne’s presentation at DrupalCamp Montreal.

Thank You!

The sprint was a huge success, and a great milestone for the Montreal Drupal community. In addition to learning more about the multilingual intiative and how languages are handled in Drupal 7 and 8, we learnt a lot about contributing to Drupal, issue queue management using IRC, and how to write tests. Thanks to Francesco and Gábor for making the trip to Montreal from across the Atlantic! Thanks to Notman House for providing the venue for the first three days of the sprint, and McGill for hosting the camp and sprint over the weekend. The Drupal Association also provided us a community cultivation grant, which paid for Francesco's flight from Italy and Acquia flew in Gábor Hojtsy from Hungary as part of their sponsorship of the camp.

We’re looking forward to helping organize another sprint in Montreal in the near future!

Sep 27 2011
Sep 27

In Drupal 6, module developers often use Nodes as an all-purpose method for having objects that can be saved into the database. This hack avoids having to write a lot of boilerplate CRUD for all custom data used in your modules. Nodes in D6 are a powerful tool for this use case when combined with the flexibility of CCK's custom fields, and the simplified integration with contrib modules. However, using Nodes for every piece of specialized content means a lot of overhead; contributed modules often modify Nodes and added hooks that operated on them. This is a major performance concern, not to mention a bad architecture for unrelated data.

In Drupal 7, we have entities. Entities not only provide all functionality available with Nodes in D6, but they offer much more. Many modules in core are now using Entities for their database records, including node.module and user.module. In this post, we'll discuss what Entities are, some poignant examples of Entities, and why you would want to use them.

What is an Entity?

Entities can be thought of as the parent class of nodes. They are the abstraction of functionality common between Nodes, Taxonomy Terms, and Users (among others). You can see this very clearly by looking at the Drupal 7 version of user_load_multiple() and node_load_multiple().

<?php
function user_load_multiple$uids = array(), 
                             
$conditions = array(),
                             
$reset FALSE) {
  return 
entity_load('user'$uids$conditions$reset);
}
function 
node_load_multiple$nids = array(),
                             
$conditions = array(),
                             
$reset FALSE) {
  return 
entity_load('node'$nids$conditions$reset);
}
?>

As you can see, the loading behaviour of nodes and users is almost identical. This makes a lot of code in Drupal 7 core more maintainable and much cleaner. Building on this, the API defines a consistent set of functions for getting information such as label and uri for an entity, which was often annoying extra work in D6. One of the biggest benefits, however, is that you can define your own custom entities, and they can also be fieldable. In short, you get all of the convenience of nodes without the overhead and unnecessary code.

Entities also have revision tracking support baked in, just like the the Node module. This lets you have multiple versions of the entity stored in the database, but without having to define the revision-loading logic yourself. For example, if you made a “Wiki page” entity, you could track changes to pages and, with some ingenuity, show a diff between two revisions. Setting up these tables and writing boilerplate SQL and loading code would be time consuming and error-prone, but with entities, you can get Drupal to d the hard work for you.

Examples of entities

Some notable Entity types are Nodes, Users, and Taxonomy Terms. However, many contributed modules have moved to Entities for this kind of functionality. Here are some notable modules that use Entities:

  • Wysiwyg: Uses entities for profiles.
  • Organic Groups: Uses entities for groups, membership types, and memberships.
  • Commerce: Uses entities for products, customers, payments, and line items.
  • Field Collection: Uses entities for “Field-collection items.” The Field collection module allows you to add fields to the “Field-collection items,” which then appear to be added to the entity that the field collection was originally attached to.
  • Relation: Uses entities for — you guessed it — relations. These entities link two other entities together. But, since relations are entities, the relationships themselves can have fields.

Benefits of Entities

Why are entities so cool? Here’s a short list:

Avoiding Boilerplate loading logic and SQL

Writing loading functions like node_load and boilerplate SQL is tedious and hard to maintain. With Entities, we can focus on the important functionality that is specific to our data type.

Consistent functionality between types

As I said before, entities have a standard interface. They have URIs, labels, and you can use a standard function to load them. This means more flexible, reliable code.

Not having to work with Nodes

Not having to work with Nodes for every main chunk of data makes your architecture better. Separating Node functionality from your own module’s functionality leads to more stable code. Even contributed modules that abuse nodes won’t get in your way. And the multitude of hooks running on Node operations can be trimmed down.

The contrib Entity API Module

The confusingly named contrib Entity API module provides even more functionality than Drupal core. In addition to all the benefits mentioned above, it provides saving, deleting, and updating entities, along with a ton of extra functionality. We’ll talk about this in the next part of this series.

Related Links

Want to learn more about Entities? Here are some links to get you started.

  • Ronald Ashri of Istos has an excellent three part series on building entities.

Slides and Video

At Drupalcamp Montreal, I gave a presentation of most of this. The slides are embedded and the video is available here: Videos

Sep 23 2011
Sep 23

After 6 months of planning, DrupalCamp Montreal finally came together mid-September. We were all really excited for how this year would pan out. With a smaller, leaner organizational team, the core group of volunteers quickly secured a location, set up a website and selected keynotes. McGill University generously provided McIntyre Medical building as our venue, and we hosted a pre-Camp code sprint at the Notman House.

Gabor Hojtsy and Francesco Placella flew in from Hungary and Italy, respectively, for the sprint. Indeed, it proved to be a very productive week for those involved. Over 15 issues were created, UX improvements were logged, and the multilingual documentation received a major facelift. Despite being scheduled for only 3 days, most of the sprinters continued their effort during the Camp.
Code Sprint

The Camp itself went off without a hitch. Attendance, unfortunately, wasn’t as high as we had expected, but the smaller crowd created a more personal feel at the event. We had two powerful keynotes delivered by Jen Simmons and Angie Byron, and a host of presentations by knowledgeable speakers. Jen gave an overview of the progress made in responsive design and what the future holds with HTML 5. Angie talked about how every little bit goes a long way in the community, and how easy contributing to Drupal can be. Both were inspiring speakers whose presentations should be watched by everyone (videos are available here).

Despite the smaller crowd, the Camp generated a profit for the second year in a row. Montreal is a great place to get involved in technology, and our local Drupal community is steadily growing. The funding from the event will provide us with sufficient resources to host more code sprints, meet ups, community BBQs and to foster Drupal growth in the city.

Evolving Web presented 5 sessions during the Camp. All videos are up on the website, and our slides are available below:

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