Stephen Barker of Digital Frontiers Media just finished up co-authoring a tutorial on the use of the Hierarchical Select (HS) module for Drupal with the module's author and maintainer, Wim Leers. Wim took time from his final exam preparations (thanks and congratulations, Wim!) to help Stephen understand the details of the module and Stephen documented this information in the form of an extensive description on the use of Wim's HS API. The two then worked together to shape it into a tutorial that will hopefully make integration of the module into other works easier.
The entire tutorial appears below and has also been added to the Handbook section at Drupal.org here.
How to Use Hierarchical Select for Something Other than Taxonomy
For those of you who haven’t yet used the Hierarchical Select (HS) Module for Drupal by Wim Leers, it provides a Forms API compliant method of placing hierarchically-dependent dropdowns within your Drupal forms without resorting to ‘markup’ or needing to know AJAX or AHAH. In fact, all you really need to know is the organization of your own hierarchical tree and a few steps to define the dropdown selects using HS.
For those who have used HS before but have only used the hierarchical taxonomy functions it comes with and yearn to use it for other things in your own modules, read on. This tutorial will walk you through the steps of integrating it with your custom module.
Let’s say you are looking for a solution to a problem for neatly and easily choosing a contact’s name on a form, based on the company they work for and which division or department they’re in. HS is the answer! But figuring out how to use the API to get it to produce a non-taxonomy-based select widget might not be immediately intuitive. The HS module provides a very descriptive API. However, one might make erroneous assumptions leading to frustration and wasted time. This tutorial will lead you through the API for such applications to help you get the most out of HS module with the least amount of frustration.
In HS3, more details are provided about its API to help figure things out along with a “terminology” lexicon to help eliminate misunderstandings. This tutorial assumes you are using the HS3 API (and its accompanying documentation: API.txt) and from here forth will use the language defined in that document’s “terminology” section where appropriate.
For purposes of this tutorial, we will create a hierarchical select for the problem we presented above where one chooses a contact’s name by first choosing the company they work for, then the department or division they’re in, before reviewing a list of the appropriate contact names.
The first step is to define the HS widget in your module using Drupal’s Forms API. For our example, the form item would be defined in its simplest form like this in the appropriate location of your module:
$form['hs_tutorial_select'] = array(
'#type' => 'hierarchical_select',
'#title' => t('Company | Dept/Div | Contact'),
'#config' => array(
'module' => 'hs_tutorial',
'params' => array(
'root' => 'root',
'enforce_deepest' => 1,
'level_labels' => array(
'status' => 1,
'labels' => array(
0 => t('Choose:'),
'#required' => TRUE,
The main differences between defining an HS form element and a standard form element is that the ‘#type’ property is defined as ‘hierarchical_select’ and there is an HS-specific property called ‘#config’. Within the #config array are the parameters that define for the most part how the hierarchical select will behave and look. These parameters are pretty well spelled out in HS’ API document and I won’t go into details about them here. I’ll suffice with saying that in the case of this tutorial, we want the ‘enforce_deepest’ parameter to be set to 1 (TRUE) since we want the selection to result in a contact’s name only and not stop short, giving us only a company name or division.
You must now go about the task of providing the hierarchical data structure to HS through its hooks. Really, your data can be structured in any way you like. It can be in arrays or objects; it can even be flat. It doesn’t really matter since you write a routine in your module using HS’ hooks to transform it to the array structure required by HS. Thus, you need not change your existing data structure just to use HS. However, there are some aspects to hierarchical structure using HS that should be taken into account to avoid problems.
Essentially, each select deals with item-label pairs where the label is what appears in the dropdown and the item is the actual corresponding form value. The labels and corresponding items may be the same or different than each other. It doesn’t matter. In our scenario, we would need to have a set of item-label pairs for our company selection, a set of item-label pairs for our department/division selection, and a set of item-label pairs for our contact name selection.
Since HS uses the item—not the label—as an array key and index of its internal build of the hierarchy, each item must be unique and not appear elsewhere in the hierarchy. That seems straightforward enough, right? But consider the following diagram:
“Accounts Receivable” and “Accounts Payable” appear in two different places in the hierarchy—once for Company A and once for Company B. Both companies have the same types of departments.
So how do we deal with this within HS without making one of the companies rename their departments? Well, the lineages of these departments are completely unique, and they can be used to construct the item so as to ensure that its value is also completely unique. Placing hyphens between the lineage values can be done to achieve this and produce a unique item that is also human readable.
Here is an example of how this would play out in our scenario in the case of our department/division item-label pairs:
As you can see, no item definition is repeated, and the user will only see the clean department titles in their dropdown selection list (see L. 124 & 130 of the accompanying hs_tutorial.module).
As long as you address these issues, then you can start feeding HS your data. Start with defining the _hierarchical_select_params() hook, which returns an array of parameters that are required for your implementation, if any. This is useful if your module has multiple hierarchies (e.g. vocabularies) or ‘hierarchy modifiers’ (e.g. a ‘depth’ parameter) which would require the vocabulary id or a nonnegative integer to be passed, respectively.
The second thing you need to provide is an array containing the item-label pairs for the root items in your hierarchy where the array keys are the items and the values are the labels. You return this array through HS’ _hierarchical_select_root_level() hook (L. 157-164). In our scenario, we would return the following as our root options:
['Company A'] => 'Company A'
['Company B'] => 'Company B'
In the accompanying hs_tutorial.module file, we simply call the function hs_tutorial_hierarchy() (L. 161) with the hierarchical_select_root_level() hook to build our data structure. We can then retrieve the resultant items of the root level from the array $hierarchy['root']['children'] (L. 162).
However, this only gets us the items and not the associated labels. For that, a separate helper function exists: hs_tutorial_items_associate_labels_with_items() (L. 216-235). Given an array of items, it retrieves the corresponding labels. The helper function then returns an array of options for the select to HS where the items are the keys and the labels are the values (i.e. item-label pairs): $options[$item] = t($hierarchy[$item]['label']) (L. 231).
Next, you also need to provide a method for returning the immediate children for a given parent to HS in an array of item-label pairs where the array keys are the items and the values are the labels. This array needs to be returned by HS’ _hierarchical_select_children() hook (L. 166-173). This hook receives a $parent parameter from which you can build the array of children item-label pairs. Thus, in our example, if given “Company A” through the $parent parameter, we would need our method to return the following:
['Company A-Accounts Receivable'] => 'Accounts Receivable'
['Company A-Accounts Payable'] => 'Accounts Payable'
In the accompanying hs_tutorial.module file, we do this with a code structure similar to the one we used for getting the items of the root level. We call the hs_tutorial_hierarchy() function, but here the children are at $hierarchy[$parent]['children']. We then pass that through hs_tutorial_items_associate_labels_with_items() and then pass that to HS.
HS then produces the entire hierarchy as needed through an iterative process from there on.
In hs_tutorial.module, an effort was made in both the user-visible hierarchy (L. 82-107) and the data structure creation (L. 109-136) in hs_tutorial_hierarchy() to make it obvious how a hierarchy should be transformed to work with HS. HS’ hooks in hs_tutorial.module work directly upon the data returned from hs_tutorial_hierarchy() to make it as clear as possible where all the data comes from. Hopefully, this will make it easier for you to quickly adapt these to work with your own data structures.
The main chore of providing the data to HS is now behind us. However, a few more items remain that need to be taken care of before everything will work as expected. First, HS provides a data validation hook (_hierarchical_select_valid_item) that must return TRUE for the given item and params passed or the options will not be available in the hierarchical select. This is necessary to ensure that only valid items are present in the selections that are in the dropbox. HS calls this hook for every item in the dropbox to prevent forged input.
The last thing remaining is actually particular only to our given scenario. Due to the logic implied by setting the hierarchical select parameter ‘enforce_deepest’ to be active, we must also provide a function for returning the lineage of an item: _hierarchical_select_lineage(). Without this hook, HS would have to iterate over the entire hierarchy to reconstruct the lineage, and this would be a serious performance hit, or even impossible to do in the worst case.
The code here can either be a function that iteratively finds the parents of an item and returns the results in an array of items, or a function that simply splits according to a separator (as is the case for hs_tutorial.module because parental information is embedded in each item).
While there are plenty of other options available, nothing more should be required to enable you to start using HS as a solution in your own custom modules. HS3 provides a “Developer Mode” to help you debug and track what HS is doing along the way if you have the Devel Module installed and running (see HS’ API.txt file for details). Hopefully, you will find many opportunities to use this great widget in your own modules in many different ways! Happy hierarchical selecting!