Interface Module Part Two

Error message

The spam filter installed on this site is currently unavailable. Per site policy, we are unable to accept new submissions until that problem is resolved. Please try resubmitting the form in a couple of minutes.

Editor's note: This is a long post. If you just want to see the screencast, click here, and there are notes about it below. If you want some sample code to help you understand, click here. If you want to read how Mike explains the whys and wherefores, get comfortable, ease the seat back, and read on.

If you build Drupal sites for a living, you already know that the administrative interface is one of the areas of a site that gets the least attention from developers. There are a lot of reasons for this, ranging from the fact that it works (therefore, it does not need to be pretty), to the fact that modifying the admin interface is labor-intensive (there are some sections you almost need to hack core to modify), to the fact that budgets don't always cover the costs of making the backend look nicer (and we all know about the bottom line).

This year, Trellon set out to address some common usability concerns in Drupal based on feedback received from clients. Something we often hear is that Drupal is very attractive from a functional standpoint, but Joomla is more user-friendly and has a nicer interface. This misperception about the underlying power of Drupal's theming layer is chiefly an aesthetic concern, it is something that can be resolved, and an ideal solution is one that reduces the overall time and complexity involved in deploying sites featuring more usable interfaces. As a nice to have, whatever usability solution is designed can be extended beyond the web browser into other mediums such as widgets and mobile phone applications.

Given that there are only 24 hours in the day and there is so much to the administrative backend, we needed to pick our battles. The administrative component that gets the most use is the content entry interface. This presents a big target for improvement, as users are most likely to benefit from usability enhancements simply because this is the place they visit the most frequently. Another reason we wanted to work on the node entry interface is that we figured that it is the area where we can do the most cool AJAX-y stuff and have a good time doing it. The work we are doing with the content entry interface could potentially be expanded to cover other parts of the system as well, so this looks like a good place to start.

We have been focusing our efforts in this area with the development of a new module, the Interface module. In our first post about Interface, we took a look at how this module can be used to manage the layout of node forms in a Drupal site. In this screencast, we are demonstrating the way the module works, and including live demos of content being entered into the system. You can start the screencast by clicking on the image below.

What You are Looking At

Out of the box, a Drupal node form is like a perfect game of Tetris, with every box stacked neatly on top of others in a big silo. You can theme content entry forms to look different ("less Tetris, more Q-Bert" is what we say) the same way you can theme pages and views, but this task can become labor intensive with a large number of content types and require maintainence over time as form defintions can change.

What Interface does, in essence, is allow you to put form elements into regions using a drag and drop interface. Regions are defined in a template file, and you can have different regions in your interface depending on the template you choose to use. Templates can be simple, and just define a set of regions, or they can be complex, and modify the way Drupal displays pages. Interface just makes it a lot easier to modify the way forms look and work, without really needing to do any programming. You will see how this plays out in two different ways.

In the first part of the screencast, we create a new interface for the page content type then navigate to the content entry form to see it in action. You will notice the administrative panel appearing at the bottom of the page. The administrative panel is used for a lot of things. When you click on a form element, it displays some information about selected attributes. It also lets you assign actions to form elements. While this is not demonstrated here, actions are a method of modifying the way a form element is presented to users. For instance, if you wanted to put a couple of form elements in a dynamic tab set, you would select an action from a drop down menu that would tell the form element to now be a tab.

In the second part of the screencast, we start modifying the interface for the story content type. You will notice we are using a template called Joomla, and that is exactly what it is, an interface that duplicates the layout and design of the content entry forms for the 1.0 version of the Joomla platform. Right now, this is not complete, the fieldsets off to the right are not displaying in tabs. But we are making progress, and people will be able to dump form fields into tab sets when the actions API is complete.

We built the Joomla template in as a way of showing how templates can be used to modify a lot more than the placement of form elements, you can build a template that completely changes the look and feel for that part of the site. Thinking about it from a themer's point of view, you would need to write a preprocessor to change the template file, a template for the form, spend time thinking about variables, etc. Maybe you can get this done quickly, maybe it will take a lot of tinkering to get it right. Interface seeks to make this a lot simpler through the use of uniform layouts and presentation elements that can be deployed over multiple content types. While a themer may have to write an original template to get things to look the way they want, it could be reused for every content type in a Drupal web site.

How It All Works

The Interface module is in a pretty advanced stage at this point and almost ready for release. As you might expect, working with the Forms API at a low level and designing an AJAX drag and drop interface to interact with it is pretty complicated. We planned on showing off and releasing an early version of the module at Drupalcon, but decided there were enough nuanced details that still needed to be worked out before putting it out there.

The patience has paid off. Interface has been designed with end users, themers and developers in mind. While a new user can get started right away with one of many prepackaged templates, we have focused on making the module powerful enough to extend the default functionality to handle new use cases without the need to fundamentally alter the underlying module code. The structure of the module, and the way it has been put together, should be interesting to anyone who plans working with it.

Here is some sample code to help you understand the rest of the discussion. This file includes a sample template file, info file and behavior file.

First off, Interface was designed to present themers with a familiar structure for creating new layouts and simplifying the way theming occurs. Every template comes with a template.tpl.php file used for telling Drupal where the droppable regions are. While you can include as much PHP in the template files as you want, all you really need is some basic HTML and CSS to put together a set of regions for displaying form elements.

Every template also uses a .info file to tell Drupal some information about the template itself. This includes the name of the template, the tpl.php files being used, any css files associated with the template, information about the regions, and installed behaviors associated with use of the template. Most of these settings are optional, and the ones that aren't are pretty straightforward.

Secondly, for developers, Interface was designed to be extensible, and creates new methods for interacting with and displaying dynamic form elements. Interface expands the options developers have for modifying form elements through the use of behaviors and actions. Think of a behavior as a way of telling Interface how to react to various situations people will encounter while editing an Interface. There could be a new CCK field Interface does not recognize, and a behavior would be used to tell the system how to handle it. Maybe you need to tell Interface to ignore certain form elements altogether, and only allow people to alter the placement of the wrapper around them. Behaviors have hooks for exactly that.

Behaviors also define actions, which are operations that can be carried out on form elements. For instance, if you wanted to change a submit button into an image, a behavior could associate an action with all submit buttons that, when activated, allows users to upload an image to the site and replace the button with an image when the form is generated. Actions are designed to be context specific, and behaviors use dynamic CSS selectors to determine what actions can be associated with what elements. A more complex example (and one that is keeping me up nights) is tabs. When you wrap a form element within a tab set, you need to be able to carry out various tasks within the tab set, that include dropping new form elements in, selecting the content of individual tabs, and more. Behaviors allow you to organize the way the system responds to events and associate the right actions with the right elements.

Developers, We Have an API Here

As described above, Interface comes with an API for extending it. To keep it simple, we designed the behaviors system to operate using a set of hooks similar to those found within Drupal.

Every behavior is a behavior, each one uses Drupal.behaviors to register the component and intialize itself when authoring an interface or entering content. A lot of information is dumped in Drupal.settings to tell Interface what is going on, and we plan on releasing a full API guide with the module that explains how all this works. In a real basic sense, you have to tell Interface what can be positioned, what can't be positioned, and where you can put stuff.

Within the behavior code, there are a number of hooks designed to allow Interface to detect and do various important things. Interface behaviors are pure AJAX components that are triggered by Interface at certain times.

Hook_register_grouper is one of them. In a standard Drupal form layout, every form element is enclosed in something, except for submit buttons. There are times when it becomes important for Interface to understand what is wrapped around the form element, so that it can tell Drupal where to put the form element when the form is rendered. Hook_register_grouper creates a category of markup, called a 'grouper', that is used whenever a form element is located within an element that exist as it's own entity in the form array used to construct the form. Here is an example from the common_groupers behavior:

<script>
/**
* Registers a grouper. hook_register_grouper tells interface that a form element appears within a grouper
* (or is a grouper itself) and that form elements placed within it are visible. It should return some
* text for display within the status bar on the panel of the authoring interface.
*/
Drupal.common_groupers_register_grouper = function(markup){
  if(markup.tagName == 'FIELDSET'){
    element = $(markup);
    return 'FIELDSET.' + element.attr('id');
  }
  return false;
}
</script>

This hook receives a single parameter, markup, and checks it to see if we are looking at a FIELDSET. If so, it reports back to the calling function what it has found to display in the status bar of the administrative panel. This hook does not need to be in every behavior, just the ones where the position of a form element relative to a grouper is important (there are some where it is not, like with form image buttons).

Hook_register_global_selector is another important one. When you look closely at the CSS and think about how it relates to form arrays, there are a lot of permutations of form elements that can come out of Drupal. By default, Interface knows to look for regions and form elements wrapped in a DIV.form-item tag and let people drag and drop them. For everything else, we let behaviors tell Interface what to associate with the drag and drop activity. Hook_register_global_selector provides a means to do that. The sample you see below is fairly simple, some behaviors will pass back complex selectors to handle

<script>
/**
* Registers a selector for use in the authoring interface. This hook provides a common
* method for adding new selectors to the drag and drop interface.
*
* returns an array, containing the CSS selector for each item to be added to the drag and drop.
*/
Drupal.common_groupers_register_global_selector = function(){
  // create an array of selector values
  var selectors = ["FIELDSET"];
  // return these items
  return ( selectors );
}
</script>

Not too scary.

There are other hooks, this is really just a preview of what is to come. Again, we plan on releasing full documentation for the API along with the Interface module to get people started. Something I really hope to see is people using and contributing to the module over time, and this includes the design and construction of the API. Behaviors are the only thing in Interface that is 'new' for someone who has been been building custom themes and modules in Drupal, and we have gone to pains to see to it that the underlying framework is similar to the way other parts of the Drupal platform are constructed.

One of the nice to haves are are hoping to achieve with this module is the ability to deploy content entry interfaces into things besides web browsers, such as widgets and mobile phone applications. We have given a lot of thought to how Interface can be extended to create interfaces for alternative platforms. While you can do this already, Phase II of the Interface module will be to make it simple to create content entry forms for multiple devices. We have created some internal Drupal API stuff as well for this purpose, which I will go into in the next screencast.

Until next time.