Art Institute of Chicago's first brush with Drupal
The Art Institute of Chicago (AIC) is one of the world's premier art museums, best-known for its collections of Impressionist, Post-Impressionist, and American paintings. In addition to housing famous paintings such as Seurat's A Sunday Afternoon on the Island of La Grande Jatte, van Gogh's Bedroom in Arles and Grant Wood's American Gothic, the Museum also has world-class collections of photography, arms and armor, ceramic figures, portrait sculptures, miniatures, and galleries of ancient Egyptian, Greek, and Roman artifacts, including the mummy of Paankhenamun.
Palantir.net has worked with AIC since 2005, when we helped them develop and implement their site redesign and CMS integration. Their existing site only showed a small selection of material from their collection; the goal of this project was to integrate the site with their backend asset management system to allow users to browse the Museum's entire collection online, as well as allow the Museum to quickly and easily create micro-sites to promote exhibitions.
Palantir worked on the AIC Collections site in collaboration with design partner Studio Blue, who provided the original design direction for the AIC Web site. Larry Garfield (Crell) and Colleen Carroll were the joint architects of the site, with Larry working as lead programmer and Colleen as lead themer. Tiffany Farriss provided strategic leadership for the project.
Remote data
The main challenge with the AIC Collections site was, of course, their large existing collection data. It was contained in a digital asset management system (DAM) called CITI. Integrating third party data without a complete import is an area where Drupal is somewhat weak right now, but there are several ways to go about it.
We had previously implemented lazy-node-creation for the Indianapolis Museum of Art, because we needed to leverage a lot of node-based features on artwork. For AIC, that wasn't the case. We didn't really need node functionality, so a lazy-instantiation system would have been unwanted overhead. Instead, we built a parallel system specifically for AIC's data model.
We were accessing CITI via SOAP requests, using RPC-style queries rather than methods. The return values from the queries, like SQL, are somewhat raw. So rather than process them everywhere, we built a simple object-relational mapper (ORM) framework on top of them, using the much stronger OOP support in PHP 5.
The ORM ended up being quite large, but still reasonably streamlined. There were several different types of data we had to support. They all had the same basic structure which was, fortunately, fairly simple:
Artwork: A work of art held in the DAM, such as a painting or sculpture. They may be owned by the Art Institute or on loan from another institution.
Artists: The creator(s) of a given artwork, where known.
Resources: Auxiliary pieces related to various artwork, such as PDF files or short video clips.
Categories: CITI has its own categorization system and hierarchy. Categories included different departments (such as Asian art or textiles), as well as groupings within a department (such as specific time periods or cultural regions). Artwork can be in multiple categories too, making it roughly analogous to Drupal's taxonomy system.
To reduce traffic to and from the SOAP server, we designed the system to always load multiple objects at once. Loading an object did not, itself, load any of its fields. It just created a stub object. Upon calling getField(), we could then lazy-load that field for all objects in a "set" in one request, or lazy-load multiple fields for multiple objects all in one go. The result was a very efficent system that minimized SOAP database traffic while presenting a very clean and consistent API interface. We also were able to separate off search functionality to a separate operation. This in turn allowed us to keep that a clean interface as well.
If that all sounds familiar, it should. It was the success of the Collections site that prompted our previous Musings on a Data API article. It also served as a sample use case for the Data API Design Sprint that Palantir hosted in February and was a direct ancestor of the "Model 2" proposal.
Menus
Pulling content in dynamically from a third party system also required some fancy footwork with the menu system. The list of museum departments and sub-categories in the left navigation menu is mostly dynamic, and is derived from CITI.
Each department is represented as a node, which references a department category in CITI. That allowed us to have Drupal-local images for each department, while the descriptive text is pulled from CITI. Every time a department node is edited, the system updates the corresponding menu item in Primary Links and checks CITI for any sub-categories that are present. Each of those gets a menu item dynamically created. It is a bit unconventional, but the limitations of the Drupal 5 menu system didn't allow for anything else. The Drupal 6 menu system would have made it even slicker, but sadly that was not an option for us.
Foreign Theming
The Art Institute's main site is actually managed through a design-time CMS called Collage. One of the main goals for the Collections site was for it to slip seamlessly into the design. To that end, we configured the system so that page templates are actually managed from Collage and pushed out during that system's deploy cycle to the Collections site's theme directory. That way, a Collage administrator can make a global change to, say, the the main site navigation template in Collage and the change will automatically propagate to the Drupal Collections site.
Form theming
The Collections site included some of the most un-Drupal-like forms that we have had to build. Fortunately Drupal's form system is flexible enough to handle it. First, we attached custom theming functions to selected parts of the form structure (frequently pieces that were repeating). We could then theme them as a group to put into, say, an unordered list or table. That page, for instance, is generated by this code:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">foreach (</span><span style="color: #0000BB">$artworks </span><span style="color: #007700">as </span><span style="color: #0000BB">$artwork</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$artwork_ids</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$artwork</span><span style="color: #007700">-></span><span style="color: #0000BB">id</span><span style="color: #007700">();<br> </span><span style="color: #0000BB">$form_item_id </span><span style="color: #007700">= </span><span style="color: #DD0000">'artwork_'</span><span style="color: #007700">. </span><span style="color: #0000BB">$artwork</span><span style="color: #007700">-></span><span style="color: #0000BB">id</span><span style="color: #007700">();<br> <br> </span><span style="color: #0000BB">$form</span><span style="color: #007700">[</span><span style="color: #DD0000">'list'</span><span style="color: #007700">][</span><span style="color: #0000BB">$form_item_id</span><span style="color: #007700">] = array(<br> </span><span style="color: #DD0000">'#theme' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'citi_artwork_illustrated_list_form_item'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'#tree' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br> );<br> </span><span style="color: #0000BB">$form</span><span style="color: #007700">[</span><span style="color: #DD0000">'list'</span><span style="color: #007700">][</span><span style="color: #0000BB">$form_item_id</span><span style="color: #007700">][</span><span style="color: #DD0000">'artwork_id'</span><span style="color: #007700">] = array(<br> </span><span style="color: #DD0000">'#type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'markup'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'#value' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$artwork</span><span style="color: #007700">-></span><span style="color: #0000BB">id</span><span style="color: #007700">(),<br> );<br> </span><span style="color: #FF8000">// The various fields in the artwork just get added as normal markup elements<br> // so that they are "part of" the form, but just for display.<br> </span><span style="color: #007700">foreach (</span><span style="color: #0000BB">$artwork</span><span style="color: #007700">-></span><span style="color: #0000BB">fields</span><span style="color: #007700">() as </span><span style="color: #0000BB">$field </span><span style="color: #007700">=> </span><span style="color: #0000BB">$value</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$form</span><span style="color: #007700">[</span><span style="color: #DD0000">'list'</span><span style="color: #007700">][</span><span style="color: #0000BB">$form_item_id</span><span style="color: #007700">][</span><span style="color: #0000BB">$field</span><span style="color: #007700">] = array(<br> </span><span style="color: #DD0000">'#type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'markup'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'#value' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$value</span><span style="color: #007700">,<br> );<br> }<br> </span><span style="color: #0000BB">$form</span><span style="color: #007700">[</span><span style="color: #DD0000">'list'</span><span style="color: #007700">][</span><span style="color: #0000BB">$form_item_id</span><span style="color: #007700">][</span><span style="color: #DD0000">'checked'</span><span style="color: #007700">] = array(<br> </span><span style="color: #DD0000">'#type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'checkbox'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'#title' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Add to my collection'</span><span style="color: #007700">),<br> );<br>}<br></span><span style="color: #0000BB">?></span></span>
Note, in particular, the '#theme' keys. These allowed us to custom theme selected parts of the form. The second trick was to break the form or parts of it out into a template file. For that we had to override our own theme function:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">function </span><span style="color: #0000BB">phptemplate_citi_collection_artwork_form_item</span><span style="color: #007700">(Array </span><span style="color: #0000BB">$form</span><span style="color: #007700">) {<br> foreach (</span><span style="color: #0000BB">element_children</span><span style="color: #007700">(</span><span style="color: #0000BB">$form</span><span style="color: #007700">) as </span><span style="color: #0000BB">$element_key</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$vars</span><span style="color: #007700">[</span><span style="color: #0000BB">$element_key</span><span style="color: #007700">] = </span><span style="color: #0000BB">drupal_render</span><span style="color: #007700">(</span><span style="color: #0000BB">$form</span><span style="color: #007700">[</span><span style="color: #0000BB">$element_key</span><span style="color: #007700">]);<br> }<br> return </span><span style="color: #0000BB">_phptemplate_callback</span><span style="color: #007700">(</span><span style="color: #DD0000">'citi_collection_artwork_form_item'</span><span style="color: #007700">, </span><span style="color: #0000BB">$vars</span><span style="color: #007700">) . </span><span style="color: #0000BB">drupal_render</span><span style="color: #007700">(</span><span style="color: #0000BB">$form</span><span style="color: #007700">);<br>}<br></span><span style="color: #0000BB">?></span></span>
That gives us a rendered form of every sub-element in that part of the form, which can then be placed using a template file. If templating the entire form, we can easily special case out form_id and form_token so that the template author does not have to worry about them, since their placement is irrelevant.
The result is forms that don't look a thing like Drupal, but can be laid out using normal templating.
My Collections
One of the features the Art Institute requested in order to be Web 2.0-compliant was the ability for users to create their own personalized collections. My Collections allows users to flag artwork they like and build an on-site bookmark collection. Each collection can be printed off, annotated, or forwarded to friends. (Perhaps a suggested personalized tour for a date?)
For My Collections, we did use Drupal's node system. Each "collection" node references various artwork by ID and can include a user's personal comments on it. Comments are handled via a custom form field for each piece of artwork. They are displayed right on the node view page, in place of the normal page body. Rendering a form in place of the node body is actually very simple, requiring only a little work in hook_nodeapi().
Exhibitions
Another feature of the Collections site is integrated micro-sites for temporary exhibitions at the Art Institute. AIC wanted the ability to roll out mini-sites with a different theme for each of their major exhibits. In the past they had created each one manually, and that simply did not scale. Instead, they wanted a Drupal-integrated push-button way to throw up a new site for an exhibition using a different theme than the main site and a custom skin for each exhibition.
We considered several alternatives. Traditional multi-site was too complex. It required bugging the over-worked server admin every time a new exhibition needed to be setup. Table prefixing within a single database with multi-site would create a maintenance problem as the number of exhibitions grew, and would still require admin involvement. We also looked into Organic Groups, but unfortunately at the time we were building the site (late 2007) OG still had far too much dependence on the Groups.Drupal.org workflow. It also offered no clear way at the time to create separate skins for each group; we didn't want to force site admins to setup a whole new theme for each exhibition.
In the end, we rolled our own custom setup using Organic Groups as a model. Each exhibition is a single node, on which a large number of fire-and-forget settings are included . Each exhibition includes: a title, a "short title" for use in URL paths, a splash page body and graphics, a list of works that are included in the exhibition (which may or may not be in the Art Institute's own collection), and even a KML file for selected exhibitions to show where various artwork was created using Google Maps. A series of custom callback pages then pull out various bits of information from the exhibition to show on dedicated pages (such as the Map page or the Selected artwork page).
Remaining generic pages use a simple node-reference to the exhibition they relate to, and can be placed arbitrarily into a corresponding menu by the admin.
Each exhibition node also includes a series of simple color fields. Those fields are filled in with color hex values using the Farbtasic color wheel jQuery plugin, the same one used by Garland in Drupal core. Later, any page view that corresponds to that exhibition will create a corresponding CSS file if it doesn't exist already and queue it up just like any other CSS file. The result is that admins can skin each exhibition separately without ever touching code and without creating multiple themes. (Thanks to Dmitri Gaskin for the blog post that inspired this technique.)
Visual effects
Most of the visual effects on the site are handled through simple CSS or our old friend jQuery, using jqModal for most of the image popup effects. Much of the code pattern, if not the code, is reused from our earlier work on the Indianapolis Museum of Art.
Modules
Despite the size and scope of the Collections site, we were able to pull it off without any core modifications, save for upgrading jQuery. The bulk of the custom code involved integrating with CITI, as we were essentially using Drupal as an application framework for that portion of it.
Contrib modules
- CCK:
- Views
- External links
- XML Sitemap
- CacheExclude
- CustomError
- Forward
- Google Analytics
- Kiosk
- Login Destination
- Menu Trim
- Path redirect
- Sections
- User Protect
- Inline
Custom modules
- aic - Random customizations for this site
- aicevent - Hook into the Museum's legacy event database.
- aicexhibitions - Customizations specific to the Exhibitions micro-sites
- aicmaps - Support for the Google Maps in Exhibitions micro-sites
- citi - Integration with the CITI Digital Asset Management system
- citi_forward - Glue module to wrap the Forward module around artwork pages.
Conclusions
Since its launch earlier this year, the AIC Collections site has been received with enthusiasm by both site visitors and Museum staffers. Traffic in the Collections area of the site has more than doubled, and now counts for a majority of all of the Museum site's page views. The number of Museum works displayed on the Web site went from 2,500 to over 33,000, with more being added all the time. Nearly 1,000 My Collections have been created, many by students or teachers using the feature as an educational tool.
Although it's only the first taste of Drupal for the Art Institute it probably won't be the last, and it's a great example of how Drupal can play nicely with third-party systems on both the front-end and the back-end.
Case study written by Larry Garfield, Colleen Carroll, Evan Clossin, and George DeMet.
Drupal version: Drupal 5.x