Mar 11 2015
Mar 11

Please be aware that due to the development process Drupal 8 has been undergoing at the time of writing, some parts of the code might be outdated. Take a look at this repository in which I try to update the example code and make it work with the latest Drupal 8 release.

With the introduction of annotated plugins, a lot has changed in Drupal 8. We have a more streamlined approach to describing and discovering pieces of functionality that extend the core. Along with many other components, the former Field API (part of the larger and consolidated Entity API) is now based on plugins.

In this tutorial we will go through defining a custom field formatter for an existing field (image). What we want to achieve is to make it possible to display an image with a small caption below it. This caption will be the title value assigned to the image if one exists.

The code we write here can be found in this repository as the image_title_caption module. But let’s see how we can get to that final result.

The Drupal module

Let us start by creating a new custom module (image_title_caption) with only one file:

image_title_caption.info.yml:

name: Image title caption
type: module
description: Uses the image title field as a caption
core: 8.x
dependencies:
  - image

Nothing out of the ordinary here. We can even enable the module already if we want.

The plugin

In Drupal 8, field formatters (like field types and widgets themselves) are plugins. Core ones are defined either by core modules or can be found inside the Drupal\Core\Field\Plugin\Field\FieldFormatter namespace. And like we’ve seen in a previous article in which we looked at custom blocks, plugins go inside the src/Plugin/ folder of our module. In the case of field formatters, this will be src/Plugin/Field/FieldFormatter directory.

Below you can see our own formatter class:

src/Plugin/Field/FieldFormatter/ImageTitleCaption.php:

<?php

/**
 * @file
 * Contains \Drupal\image_title_caption\Plugin\Field\FieldFormatter\ImageTitleCaption.
 */

namespace Drupal\image_title_caption\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatter;

/**
 * Plugin implementation of the 'image_title_caption' formatter.
 *
 * @FieldFormatter(
 *   id = "image_title_caption",
 *   label = @Translation("Image with caption from title"),
 *   field_types = {
 *     "image"
 *   }
 * )
 */
class ImageTitleCaption extends ImageFormatter {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items) {
    $elements = parent::viewElements($items);
    foreach ($elements as &$element) {
      $element['#theme'] = 'image_title_caption_formatter';
    }

    return $elements;
  }

}

This is our plugin. Nothing else to it. Above the class declaration we have the @FieldFormatter annotation through which the plugin gets discovered. We specify a plugin ID (image_title_caption), label and an array of field types this formatter can be used with. In our case, the latter only contains the default image field type but we could support more if we wanted to, even custom field types. The values that go in that array are plugin IDs so if you look at the image field type plugin, you’ll see that its ID is image.

The class looks simple because we are extending from the default ImageFormatter plugin defined by the core Image module. For our purpose, all we need to override is the viewElements() method which is responsible for returning a render array of our field data. The latter can be found inside the $items list and can be used and prepared for rendering.

The first thing we do in this method is make sure we call the parent class method on the items and store that in a variable. That will already prepare the image to be rendered just as if it would normally.

By default, the ImageFormatter plugin (the parent) uses the image_formatter theme inside the render array to output the image field values. What we do here is that for each item, we replace this theme with our own: image_title_caption_formatter. Then we return the elements (render array) just like the parent does.

You’ll notice this a lot in Drupal 8: we get a very good indication on what we need to do from the parent classes we extend. And if you ask me, that is much better than figuring out what some magic hook or function does.

The theme

Since the image_title_caption_formatter theme we specified above is so far imaginary, we’ll need to create it. Inside the .module file of our module we need to implement hook_theme:

image_title_caption.module:

/**
 * Implements hook_theme().
 */
function image_title_caption_theme() {
  return array(
    'image_title_caption_formatter' => array(
      'variables' => array('item' => NULL, 'item_attributes' => NULL, 'url' => NULL, 'image_style' => NULL),
    ),
  );
}

This should look familiar as it is very similar to Drupal 7. Please take note of the variables we pass to this theme. We intend to override the default image_formatter theme so we should have the same variables passed here as well. Additionally, since the image_formatter theme is preprocessed, we’ll need to create a preprocessor for our theme as well:

/**
 * Implements template_preprocess_image_title_caption_formatter().
 */
function template_preprocess_image_title_caption_formatter(&$vars) {
  template_preprocess_image_formatter($vars);
  $vars['caption'] = String::checkPlain($vars['item']->get('title')->getValue());
}

In this preprocessor we perform two actions:

  • We make sure that the variables passed to the template file will have first been preprocessed by the default image_formatter theme preprocessor. This is so that all the variables are exactly the same and the image gets displayed as it normally would be.
  • We create a new variable called caption that will contain the sanitised value of the image title.

For sanitisation, we use the helper String class statically. We are still inside the .module file so we cannot inject it, but we need to use it at the top of the file:

use Drupal\Component\Utility\String;

Template

Lastly, we need to create a template file for our new theme:

templates/image-title-caption-formatter.html.twig:

{% if url %}
  <a href="http://www.sitepoint.com/creating-custom-field-formatters-drupal-8//{{ url }}">{{ image }}</a>
{% else %}
  {{ image }}
{% endif %}
{% if caption %}
  <div class="image-caption">{{ caption }}</div>
{% endif %}

Similar to Drupal 7, the name of this file is important as it mirrors the theme name. As for the contents, they are almost the same as the template used by the image_formatter theme except for the caption printed at the bottom.

Does it work?

Now that we’ve written the code, we need enable the module and clear all the caches if we had made code changes after enabling. It’s time to test it out.

For example, go to the article content type field display settings at admin/structure/types/manage/article/display. For the Image field, under the Format heading, you should be able to select the Image with caption from title format. Save the form and go to admin/structure/types/manage/article/fields/node.article.field_image and make sure the image field title is enabled.

Finally, you can edit an article, upload an image and specify a title. That title will continue to behave as such, but additionally, it will be displayed below the image as a caption. Of course, you can still style it as you wish etc.

Conclusion

In this article we’ve seen how easy it is to create a field formatter and extend default behaviour in Drupal 8. We’ve only touched on overriding the viewElements() of this plugin but we can do much more to further customise things. You are also not required to extend the ImageFormatter. There are plenty of existing plugins you can either extend from or use as an example.

Additionally, you can easily create new field types and widgets as well. It’s a similar process but you’ll need to take some schema information into account, use different annotation classes and write some more code. But the point is you are very flexible in doing so.

Feb 21 2012
Feb 21

If you've recently upgraded to CiviCRM 4.1, you'll need to upgrade your webform_civicrm as well. Versions 2.3 and below are not fully compatible with Civi 4.1. Version 2.4 is, and will be released in the next couple of days, especially if I get a few comments on this post from people who have sucessfully tested it! The latest -dev is stable and working, so please feel free to download it and try it out on your 4.1 site!

Note: webform_civicrm 2.4 is not backwards-compatible with older versions of CiviCRM, and should only be used with 4.1.x

New Features In webform_civicrm 2.4

  • Adds contact image and language pref fields
  • More options for matching/updating existing activities
  • Support for new multi-valued contact sub-type
  • Improved group and tag fields
  • Can include the entire webform submission in the activity details, as well as an edit link
  • Can generate a contact checksum for use in webform-generated emails
  • Supports cid1=0 in URL, so the "not you" link no longer logs you out
  • Support custom data for cases

This version also has some behind-the-scenes enhancements to improve efficiency, compatibility, and pave the way for future improvements. As always, Drupal 6 and 7 are both supported :)

Dec 15 2011
Dec 15

I've just released a new version of the CiviCRM Webform Integration module for Drupal 6 and 7.
This module provides a flexible and powerful way to create forms linked to the CiviCRM database. Version 2 of the module is built for CiviCRM 3.4/4.0, and can create and update contacts, group subscriptions, tags, relationships, cases, activities, event participants, and custom data.
New in 2.3:

  • Contact Clone Feature - a real timesaver for multi-contact forms
  • Now works with event registration limits
  • Improves Group Subscriptions
  • Deduping works with shared addresses
  • Better and more consistent performance with Country/StateProvince chainselect
  • Other minor bug fixes

Future Plans
If you're interested in the future of this module, check out this post about supporting more entities.

Oct 16 2011
Oct 16

I've just released the stable 2.0 version of the Drupal Webform CiviCRM Integration module and wanted to share some of the cool new things you can do with it.

Version 1, which I wrote earlier this year, was basically built for a single purpose: you could have a user fill out a webform, and their contact record (name, address, email, etc.) would be created/updated and an activity of the form submission would be logged. That alone is pretty darn useful, but suggestions from users, the advent of API v3, and a commission from the core team got me setting sights higher for the next release.

New Features in Version 2

  • Handling multiple contacts and contact types

    Instead of processing a single contact per form submission, you can now have as many contacts on the form as you like, and they don't all have to be individuals. A simple use for this would be to allow a person to fill out their own information and their employer's on the same form.

  • Working with multi-valued core and custom fields

    Each contact on your form can now have multiple addresses, phone numbers, websites & emails. You can also share addresses between contacts. And multi-valued custom fields are supported as well.

  • Supporting campaigns

    If you're using the new CiviCampaign component, you can now specify a campaign for activities and events.

  • Creating relationships

    Since you can enter multiple contacts per form, why not create relationships between them at the same time? You can specify the relationship type in advance, or expose it to the form for users to select.

  • Opening and updating cases

    You can now create webforms that open a case, or add an activity to an existing case.

  • Handling event registration

    You can register multiple contacts for multiple events on a single webform. They can all be signed up for the same event(s), or register for different events. Sorry, collecting payments via webform is not currently possible.

Usage Ideas

Fancy RSVP Form

Often people want to sign up multiple people at once for an event. Webform integration can make this very slick -- send out a hashed link to the RSVP webform, and users will find their own contact details already pre-filled. You can use webform_conditional to allow them to say how many friends they're bringing, and then show/hide the right number of fieldsets with no page reload. And why not create the relationships between them while you're at it? You can expose the relationship types you want to the form to let them pick from, say, "friend, colleague, spouse, etc" for each person.
Also, the same form could allow people to RSVP that they are not coming to the event (I remember hearing that feature request on another post recently), by exposing the "participant status" field to the webform, creating a new status in civicrm for non-attenders, and then configuring that field to show just the choices you want, with appropriate labels. So "Yes I'm coming" and "Sorry, can't make it" would be what the user sees, and it would go into CiviCRM as either "registered" or "non-attender" status.
While you're at it, redirect those non-attenders to a contribution page after they submit the form, so they can still support your cause even though they're not coming in person!

Team Sign Up

You could make a form that lets the user enter an organization-type contact for their team, and specify sub-type on the form, i.e. football, lacrosse, hockey. They could then enter all the players' names, and the form submission would automatically create relationships between player and team, and the teammates with each other. It could also add them to a group so they get your email announcements, and tag them as current players.

Self-Serve CiviCase

Say your organization provides services, and tracks them using CiviCase. And say cases are typically opened or updated when a person requests a service. Enter Webform CiviCRM...
Create your service request form with the usual name and address fields, then configure your case/activity options: Choose the appropriate case type, and set the status to "ongoing". Choose an activity type like "service request."
When the user fills out the form, a new case will be created, and the service request activity will be added to the case, containing whatever details they specified. If a case of that type is already open for that person, then the activity will just be added to the existing case.

Even More Usage Ideas...

If you've been using this module already, I hope you'll add to this list in the comments:

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