Aug 26 2015
Aug 26

Based on my presentations at DrupalCamp London, on Saturday 28th February 2015 and DrupalCamp Bristol, July 4th 2015

Concept of a field

Fields are the data entry points to a web application. Usually, they provide HTML elements and may be responsible for any manipulation of data before it goes into and comes out of the application. The data captured from a single field can be simple or complex.

Assuming we want to create a field for country, all we need is a single HTML element - textfield or select options. An address field, on the other hand, is a collection of discrete data which may include standalone simple fields including a textfield representing postcode (provided by a core module) and a country field (maybe from a custom or contributed module).

Screenshot of simple and complex fields

In the Drupal world, when considering solutions the first phrase you may hear is, “there’s a module for that!”. However, for the task at hand, “you need a module for that!”. We are now going to write a module which provides this custom country field..

To create a new field in Drupal 7, a number of hooks need to be implemented in a custom module and these include the following:

  • hook_field_info() - the field type definition as well as its settings.
  • hook_field_schema() - the database schema for the field structure.
  • hook_field_widget_info() - the widget types to use for the field type.
  • hook_field_formatter_info() - the display of field values.

The task in Drupal 8 is founded on the same principles although the implementation differs. The first thing to remember here is, “there is a class for that!”. A lot of the hard work has been done in Drupal core and all we need to do is extend some classes and override default methods to suit our implementation.

Comparison of Drupal 7 hooks with Drupal 8 plugins

Creating a module

All contrib and custom modules should be placed inside the “modules” folder in your site root. Core-related code is now in “core”. However, it’s also best practice to have “contrib” and “custom” sub-folder in “modules” for clear separation of these types of modules. So we’ll create our “country” folder under modules\custom. What used to go inside *.info file is now in country.yml, so we create that file too and add the following:

name: Country
type: module
description: Defines a simple country field type.
package: Field types
version: VERSION
core: 8.x
dependencies:
  - field

Inside your module directory, you need a “src” subdirectory to keep all your class files. To organise things further, you need a “Plugin” sub-folder in “src”. There are different types of plugins e.g. fields, actions, blocks and menus. So you need to create another folder called “Field” inside Plugin and you’ll end up with a directory structure like src\Plugin\Field

Example directory structure for a Drupal 8 module

Next, we need to define our data type, widget and formatter. These will be in classes with each occupying its own folder again. Let’s take them one by one.

Data Type

The folder is called FieldType, so create it - src\Plugin\Field\FieldType. Create a class, which we shall call “CountryItem”. The file is called CountryItem.php and in it we should have:

class CountryItem {
}

How do we define our field type? With the new plugin system, this requires an annotation1 - something like a special comment block to allow core classes know about our field type. Your code should now look like this:

/**
 * Plugin implementation of the 'country' field type.
 *
 * @FieldType(
 *   id = "country",
 *   label = @Translation("Country"),
 *   description = @Translation("Stores the ISO-2 name of a country."),
 *   category = @Translation("Custom"),
 *   default_widget = "country_default",
 *   default_formatter = "country_default"
 * )
 */
class CountryItem {
}

The information provided in our annotation is quite similar to that provided when implementing hook_field_info() in Drupal 7. Next, at the top of our file, we add a few things like namespaces and import required core classes.

namespace Drupal\country\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;

Then we make our class inherit from core FieldItem class by extending it.

class CountryItem extends FieldItemBase {
}

There are two functions we must implement in our class - schema() and propertyDefinitions(). They’re as follows:

public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return array(
      'columns' => array(
        'value' => array(
          'type' => 'char',
          'length' => static::COUNTRY_ISO2_MAXLENGTH,
          'not null' => FALSE,
        ),
      ),
      'indexes' => array(
        'value' => array('value'),
      ),
    );
  }

Here we define the schema for this field. The column is to be called “value”, and will hold a 2-character string, representing the ISO-2 name of countries. Oh, don’t forget to add the constant for the length in your class:

const COUNTRY_ISO2_MAXLENGTH = 2;
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(t('Country'));
    return $properties;
  }

However, we need to add two methods to make our life easier. Firstly, we want to know when our field is considered empty which is what hook_field_is_empty() does in Drupal 7. Then, we want to add some validation so that the country code we want to store doesn’t exceed the maximum length we have defined for our schema. When we are through, our class should look like this:

/**
 * @file
 * Contains \Drupal\country\Plugin\field\field_type\CountryItem.
 */

namespace Drupal\country\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;

/**
 * Plugin implementation of the 'country' field type.
 *
 * @FieldType(
 *   id = "country",
 *   label = @Translation("Country"),
 *   description = @Translation("Stores the ISO-2 name of a country."),
 *   category = @Translation("Custom"),
 *   default_widget = "country_default",
 *   default_formatter = "country_default"
 * )
 */
class CountryItem extends FieldItemBase {

  const COUNTRY_ISO2_MAXLENGTH = 2;

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(t('Country'));
    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return array(
      'columns' => array(
        'value' => array(
          'type' => 'char',
          'length' => static::COUNTRY_ISO2_MAXLENGTH,
          'not null' => FALSE,
        ),
      ),
      'indexes' => array(
        'value' => array('value'),
      ),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this->get('value')->getValue();
    return $value === NULL || $value === '';
  }

  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
    $constraints = parent::getConstraints();
    $constraints[] = $constraint_manager->create('ComplexData', array(
      'value' => array(
        'Length' => array(
          'max' => static::COUNTRY_ISO2_MAXLENGTH,
          'maxMessage' => t('%name: the country iso-2 code may not be longer than @max characters.', array('%name' => $this->getFieldDefinition()->getLabel(), '@max' => static::COUNTRY_ISO2_MAXLENGTH)),
        )
      ),
    ));
    return $constraints;
  }
}

Data Input

Now we have defined our data type and we want to store the ISO-2 country code. How do we want users to input data? We have two options - select dropdown options and an autocomplete textfield. The select options can be the default widget.

We start by creating a class called CountryDefaultWidget in src\Plugin\Field\FieldWidget\CountryDefaultWidget.php with the following code:

namespace Drupal\country\Plugin\Field\FieldWidget;

use Drupal;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;

class CountryDefaultWidget extends WidgetBase {
}

There’s still an important thing missing from our widget class - annotation of the class as provider of a FieldWidget. Add this just above the class statement;

/**
 * Plugin implementation of the 'country_default' widget.
 *
 * @FieldWidget(
 *   id = "country_default",
 *   label = @Translation("Country select"),
 *   field_types = {
 *     "country"
 *   }
 * )
 */

This is similar to the old array keys for the old hook_widget_info() in Drupal 7. Additional annotation keys may be defined by a hook_field_widget_info_alter() function.

Our CountryDefaultWidget class isn’t complete yet. Widgets handle how fields are displayed in edit forms. The missing method we need to implement will do this for us. Add this formElement() method:

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $countries = \Drupal::service('country_manager')->getList();
    $element['value'] = $element + array(
        '#type' => 'select',
        '#options' => $countries,
        '#empty_value' => '',
        '#default_value' => (isset($items[$delta]->value) && isset($countries[$items[$delta]->value])) ? $items[$delta]->value : NULL,
        '#description' => t('Select a country'),
      );
    return $element;
}

Other modules may alter the form element provided by this function using hook_field_widget_form_alter() or hook_field_widget_WIDGET_TYPE_form_alter().

This default country widget is a simple widget of select options. Drupal core provides a list of all countries in the world as an array of country names keyed by ISO-2 country codes. This is made available as a service for use anywhere in a Drupal project.

Let’s start off with a complete implementation for this. Create src\Plugin\Field\FieldWidget\CountryAutocompleteWidget.php with this code:

namespace Drupal\country\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal;
use Drupal\Core\Form\FormStateInterface;

/**
 * Plugin implementation of the 'country_autocomplete' widget.
 *
 * @FieldWidget(
 *   id = "country_autocomplete",
 *   label = @Translation("Country autocomplete widget"),
 *   field_types = {
 *     "country"
 *   }
 * )
 */
class CountryAutocompleteWidget extends WidgetBase {
}

There’s nothing unusual here at all. We need to implement same defaultSettings() and formElement() methods as for the default widget. Add this to the class:

public static function defaultSettings() {
    return array(
      'size' => '60',
      'autocomplete_route_name' => 'country.autocomplete',
      'placeholder' => '',
    ) + parent::defaultSettings();
  }

We want a textfield that’s wide enough (‘size’ => 60). For the autocomplete_route_name key we provide the name of the route from which our autocomplete functionality will return matching values. We’ll be implementing that shortly. We don’t want anything as the placeholder.

Finally, let’s add our formElement() method:

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $countries = \Drupal::service('country_manager')->getList();
    $element['value'] = $element + array(
      '#type' => 'textfield',
      '#default_value' =>  (isset($items[$delta]->value) && isset($countries[$items[$delta]->value])) ? $countries[$items[$delta]->value] : '',
      '#autocomplete_route_name' => $this->getSetting('autocomplete_route_name'),
      '#autocomplete_route_parameters' => array(),
      '#size' => $this->getSetting('size'),
      '#placeholder' => $this->getSetting('placeholder'),
      '#maxlength' => 255,
      '#element_validate' => array('country_autocomplete_validate'),
    );
    return $element;
  }

This is a standard autocomplete widget. Looking at the FAPI array keys, some are very familiar. #autocomplete_route_name matches what we entered in our defaultSettings() a while ago. The value is retrieved from there with $this->getSetting(‘autocomplete_route_name’). The same goes for #size and #placeholder. Our #autocomplete_route_parameters has no default value. In order to ensure that the final value to be submitted doesn’t include unwanted values, we add #element_validate and enter the name of our callback function. We will also implement this shortly.

Creating a route

Create a YAML configuration file called country.routing.yml in your main module directory with the following:

country.autocomplete:
  path: '/country/autocomplete'
  defaults:
    _controller: '\Drupal\country\Controller\CountryAutocompleteController::autocomplete'
  requirements:
    _permission: 'administer content types'

The key or name of the route is country.autocomplete which is how this route will be referred to anywhere in the application. At least a route should define 3 things: path, code to execute and who can access the path.

  • path: This is the URL for our AJAX autocomplete calls.
  • _controller: This is the code we want to execute when we visit the defined path. It’s written as CLASS::FUNCTION. If our function requires any parameters, this is where they will be specified. We will be creating our controller shortly.
  • _permission: The string representation of the permission for access control

Now we move on to the creation of our controller class. Create a folder called Controller under src. Then add CountryAutocompleteController.php inside it. Add this code:

/**
 * @file
 * Contains \Drupal\country\Controller\CountryAutocompleteController.
 */

namespace Drupal\country\Controller;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Utility\Unicode;
use Drupal;

/**
 * Returns autocomplete responses for countries.
 */
class CountryAutocompleteController {

  /**
   * Returns response for the country name autocompletion.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request object containing the search string.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   A JSON response containing the autocomplete suggestions for countries.
   */
  public function autocomplete(Request $request) {
    $matches = array();
    $string = $request->query->get('q');
    if ($string) {
      $countries = \Drupal::service('country_manager')->getList();
      foreach ($countries as $iso2 => $country) {
        if (strpos(Unicode::strtolower($country), Unicode::strtolower($string)) !== FALSE) {
          $matches[] = array('value' => $country, 'label' => $country);
        }
      }
    }
    return new JsonResponse($matches);
  }
}

Whatever we type in our autocomplete widget will get passed to our autocomplete method. Then, we simply search for it in the array of country names which we pull from the country_manager service we have come across before. Finally, we return any matches or an empty array in a JSON response.

That looks more like it now and we’re nearly there. If you look back at our formElement() method in CountryAutocompleteWidget.php we specified a validation callback. We are going to do that now in our country.module file. Add this code:

/**
 * @file
 * Defines simple country field type.
 */

use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Form element validate handler for country autocomplete element.
 */
function country_autocomplete_validate($element, FormStateInterface $form_state) {
  if ($country = $element['#value']) {
    $countries = \Drupal::service('country_manager')->getList();
    $iso2 = array_search($country, $countries);
    if (!empty($iso2)) {
      $form_state->setValueForElement($element, $iso2);
    }
  }
}

We get our array of countries, and compare the value we want to send to the database with possible values. Now that we’re here, let’s just implement hook_help to give some information about our module. Just add this below the last use statement:

/**
 * Implements hook_help().
 */
function country_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.country':
      $output = '';
      $output .= '<h3>' . t('Country') . '</h3>';
      $output .= '<p>' . t('The Country module defines a simple country field type for the Field module. It provides 2 widgets - select options and autocomplete textfield - for this purpose.  See the <a href="https://capgemini.github.io/drupal/writing-custom-fields-in-drupal-8//!field-help">Field module help page</a> for more information about fields.', array('!field-help' => url('admin/help/field_ui'))) . '</p>';
      return $output;
  }
}

We have now finished our autocomplete widget and learned something about routing. Not bad at all!

Data Output

We have everything ready for creating our field and allowing users to input data. Everything should work. There’s little work left before we can display the output. That’s where the need for a field formatter comes in. Add a new folder: src\Plugin\Field\FieldFormatter and inside it create CountryDefaultFormatter.php. Then add this code.

/**
 * @file
 * Definition of Drupal\country\Plugin\field\formatter\CountryDefaultFormatter.
 */

namespace Drupal\country\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal;

/**
 * Plugin implementation of the 'country' formatter.
 *
 * @FieldFormatter(
 *   id = "country_default",
 *   module = "country",
 *   label = @Translation("Country"),
 *   field_types = {
 *     "country"
 *   }
 * )
 */
class CountryDefaultFormatter extends FormatterBase {
}

If we don’t do anything now, everything will work except where we expect to see a country name, we will see the ISO-2 which was saved as the value of the field. To display our country name, we need to override the viewElements() method. So let’s do it:

/**
 * {@inheritdoc}
 */
public function viewElements(FieldItemListInterface $items) {
    $elements = array();
    $countries = \Drupal::service('country_manager')->getList();
    foreach ($items as $delta => $item) {
      if (isset($countries[$item->value])) {
        $elements[$delta] = array('#markup' => $countries[$item->value]);
      }
    }
    return $elements;
}

Once again, we get our array of countries, find the country name for our ISO-2 value and return it as markup. Job done. The module we have created is now functional and can now be installed.

Field UI

Let’s take a look at what fields UI offer us with our Country field. You may use an existing content type or start off with a new one. From the list of content types (admin/structure/types) clicking on the Edit operation will take you to the edit form with three tabs at the top: Manage fields, Manage form display, and Manage display which give us access to our field type, field widget and field formatter, respectively.

  • Field type (Manage fields):

Click “Add field”. Select “Country” from the “Add a new field” select dropdown. That dropdown is populated by all @FieldType plugins from all enabled modules. They are grouped according to the “category” key of field plugins. This is optional, and if it isn’t defined for a field, it goes under “General”. If you inspect it, you’ll see that the name is “country_default” which matches the “id” key of our plugin and the “Country” we see there is the “label”.

Enter “Location” as the field name in the “Label” textfield and save it.

If you want to see how the field type settings are reflected in the field UI, go back to the annotation in CountryItem.php. Edit your plugin label, change it to something like “Country - edited” and save it. Then go back to the fields UI page and you’ll see not only the new value in the select dropdown when creating a new field but also under the “Field Type” column for the new Location field. You can revert the change now.

  • Field widget (Manage form display):

On this screen you can see what was defined in the FieldWidget plugin. In the URL you can see the plugin “id” and the “label” is the description.

  • Field formatter (Manage display):

There isn’t much choice here so we just leave the default formatter as is.

You may now take the completed field module for a spin. Finish by adding it to a content type, create a node and view the output. That’s all. We’ve come to the end of looking at how to create custom field modules and in the process learned a few things about Drupal 8. Go make yourself a coffee. You deserve it!

1: See https://www.drupal.org/node/1882526 for an excellent introduction to annotation-based plugins.

Aug 19 2013
Aug 19

The taxation rules that come with Drupal Commerce out of the box calculate VAT or sales tax for each line item without any conditions, so every item in the order is taxed. This works for many situations but if you have items which need to be tax exempt you're in trouble. But there is a relatively painless way around it using some rules and fields.

To start with create a new field on your product entity called Tax Exempt. Set it to be a Boolean, single on/off checkbox.

Create a field called Tax Exempt

Make sure the default value is not checked, we don't want things to be tax exempt by default. Also remember to hide this field in the Manage Display settings.

Now we have to set up the taxation rules to handle this. Make sure the Tax and Tax UI modules are turned on. Two tax types will be enabled by default: VAT and Sales tax. We're going to start by setting up a generic sales tax. Right now we're going to charge this sales tax for everything. Realistically in the United States, you need to only collect sales tax in the state you're doing business in. For more on this topic check out another post in our Drupal Commerce Tax Series, Conditional State Sales Tax.

Create a new tax, we'll call it Generic Sales Tax. Give it a rate of .07, this translates to 7%.

Create a new generic tax rate

We can click configure component and add our conditions. First we need to bring the Tax Exempt field into scope. In Rules you have to check for a field before you can query it. Our first rule will be to get to the Product entity.

Condition: Entity has field.

Data selector: commerce-line-item
Field: commerce_product

Rule condition: entity has field commerce-product

This gives us access to the product through rules. Our next rule will give us access to the Tax Exempt field.

Condition: Entity has field

Data selector: commerce-line-item:commerce-product
Field: field_tax_exempt (or whatever you called your field)

Rule condition: entity has field Tax Exempt

Now that we have access to our Tax Exempt field we can make sure tax exempt items are not taxed.

Condition: Data to compare

Data selector: commerce-line-item:commerce-product:field-tax-exempt
Operator: Equals
Data value: (checked)
Negate
What this does is create a NOT Data comparison. So as long as the product does not have the Tax Exempt field checked tax will be calculated.

Rule condition: field Tax Exempt is not checked

With these conditions taken into account when calculating sales tax from a line item creating tax exempt products is as easy as checking a box. Here are all of the tax exempt conditions together.

All conditions for exempting products from taxation

For information on how to handle sales tax by state see our article Handling Sales Tax Based on Order Address with Drupal Commerce.

Feb 28 2013
Feb 28

Episode Number: 

117

The Drupal 7 jQuery SelectBox module is a simple module that makes HTML select boxes easier to style. It replaces the HTML form select element with easier to style HTML markup.

In this episode you will learn:

  • How to download and install the jQuery SelectBox module
  • What the jQuery SelectBox module is doing in the background to make the select boxes on the site continue to work correctly

Thanks to Drupalize.me for sponsoring this episode.

DDoD Video: 

Feb 07 2013
Feb 07

Episode Number: 

104

The Drupal 7 Conditional Fields module makes it easy to build dynamic and complex forms with Drupal 7 Fields. Any Drupal entity that has fields can pretty much be built into a dynamic and conditional form. The Conditional Fields module essentially lets you set conditions for how specific fields act based on another dependent field on the form. The simplest example is that this module can hide or show a field based on the value of another field. If you are still confused at what this module can do, watch the video to find out more!

In this episode you will learn:

  • The basic Drupal conditional fields terminology
  • How to hide and show Drupal fields based on the values of other form fields
  • How to get the module to work with nested field conditions

Thanks to Drupalize.me for sponsoring this episode!

DDoD Video: 

Sep 24 2012
Sep 24

Drupal's built-in support for generating RSS feeds has long been an easy selling point. Do you have content? If so, Drupal will generate a feed of it, easy-peasy! Unfortunately, that RSS support hasn't kept up with the flexibility of Drupal's FieldAPI. The RSS format supports extensions to handle data types like file attachments, location data, and so on. By default, though, Drupal, jams all of your custom field data into the body of the RSS feed as simple text. That's where RSS Field Formatters comes in.

Screenshot of administration screen

RSS Field Formatters is a collection of slick formatters for Date, Geo Location, User Reference, Taxonomy, File Upload, and Media fields. Just set up your content type with the fields you'd normally use -- then, for the content type's "RSS" build mode, select the RSS-specific formatters for the fields you'd like to appear in proper feed elements.

Screenshot of the RSS feed

The results aren't flashy, unless you're an XML aficionado, but the improvement is important. If you're using RSS your RSS feed to move content from one site to another, the properly-formatted data fields can be imported more efficiently. In addition, geolocation and media attachments can be parsed and presented to users by many news feed readers: it's always better to pass along the data in a format they can work with.

RSS Field Formatters is smooth, easy to set up, and does a great job of sprucing up your RSS feeds with complex data that would otherwise go to waste.

*/
Jul 25 2012
Jul 25

In this lesson we cover adding different type of custom fields to your layout using Display Suite. From adding a custom code field, blocks, to dynamic fields, each of these can be moved around your layout and replicated/modified across different display modes.

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.

Dec 09 2011
Dec 09

Beginner's Guide to Drupal 7 Fields

Posted on: Friday, December 9th 2011 by Ben

As someone new to Drupal I was given a book to read and review from the team at Appnovation. The title is Drupal 7 Fields/CCK by Dave Poon, and with this post I will cover some initial impressions as I read through the first few chapters.

This book is a beginners guide to Drupal 7 and is aimed at an audience who doesn't have too much experience working with Drupal or code, although some experience is recommended. What I really liked about it is that the author thoroughly explains how the book is structured and what the end results of the project are. This helps to orient the reader and reinforce how the author intends to teach the material.

The first step is a set of instructions, with specific references, such as adding a custom field type. Following that is a a plain language explanation of what happens when you add a field. This is a great technique to explain what happens behind the scenes when you are working with the GUI. The next step is a quick quiz based on the steps just completed. Finally, there is a challenge to apply those steps on your own and experiment with the wide variety of options.

Throughout the book, the reader is working up to a complete site built with Fields. The example used is a cooking site and this provides a fun background as you work your way through it. So far, I have gone through the first quarter and I can already see the benefits of the instructions. While it is basic, it does a great job explaining how Drupal works with custom content types and how they can be used to build a wide variety of sites. This is all important knowledge for a beginner and with my next post I will share some of the more advanced techniques covered.

Dec 08 2011
rmo
Dec 08

Drupal 7’s Fields API – Initial Perspective

Posted on: Thursday, December 8th 2011 by Richard Mo

I’m sure that there's a lot of blogs, tutorials, and presentations out in the public already, which educate us about the Fields API. I’m not going to write “another blog post” about that. Instead, I'm going to share my initial impression as well as what I’ve discovered helpful to people who are new to the API.

Fields creation for content-types behaves exactly the same as CCK module of Drupal 6: selection of fields that can be attached, widget types that can be used with a field, and configuration of fields. The UI, in particular, looks almost identical to CCK module. So, a lot of the basic field types from the CCK module are part of core Drupal 7, however, like I said, it’s only the basic, if I want to have a more complex setup, I must default to something more appropriate…

From experience, CCK API is not easy to work with. If you want to create a set of textfield and textarea, group them as a whole, and allows for multiple sets. CCK can give you a headache. I’m actually quite impressed of how Fields API greatly simplifies the creation process. Although the learning curve is quite steep, yet once you learned where things are its quite easy. (As a side note, the API documentation on Drupal.org is not beginner-friendly.) So to help out our fellow Drupal 7 developers, there are five things you must implement in order for a field to work:

  1. hook_field_info()
  2. hook_field_is_empty()
  3. hook_field_widget_info()
  4. hook_field_widget_form()
  5. hook_field_schema()

The hook_field_validate(), hook_field_formatter_info(), and hook_field_formatter_view() is not required for the field to work.

Nov 04 2011
Nov 04

When working on a new Drupal 7 site I noticed it was missing a feature from the old D6 CCK days that allows you to remove a single item from a field that has unlimited values. Unfortunately, this is not built into Drupal 7's fields, and though there are a couple of threads on drupal discussing this issue, it is considered a feature request for D8 by the core developers.

In a D7 unlimited field, you 'delete' an item by emptying all of its values and saving the form. This works fine in practice but is highly unintuitive for a standard Drupal user (ie: clients) so I set out to make a simple hack-module.

This module creates a configuration page with a list of all your site's unlimited fields, allowing you to select which fields you want to apply this module to.

Functionally, the new remove button just hides the field and uses some brute force jQuery to empty the field's values. I'm not claiming it's pretty, but it gets the job done for my purposes.

Here is a link for the module.
UnlimitedField_Remove

And so there are no surprises, I've included the code below. My plan is to continue working on this by making it more drupal-like, and contributing it as real Drupal module.

Edit: Added module as a sandbox project on drupal.org here.

Module Code /* * hook_menu() */ function unlimitedfield_remove_menu(){ $items = array(); $items['admin/config/unlimitedfield_remove'] = array( 'title' => t('UnlimitedField Remove Settings'), 'page callback' => 'drupal_get_form', 'page arguments' => array('unlimitedfield_remove_admin_form'), 'access arguments' => array('administer modules'), 'type' => MENU_NORMAL_ITEM, ); return $items; } /* * hook_field_widget_form_alter() */ function unlimitedfield_remove_field_widget_form_alter(&$element, &$form_state, $context) { // some elements are buried, so we have to look for them $found = false; if(isset($element['#field_name'])){ $details = $element; $found = true; } else { // look deeper foreach($element as $key => $value){ if(isset($value['#field_name'])){ $found = true; $details = $value; break; } } } // make sure we want to apply changes to this field if($found && variable_get('unlimitedfield_remove_'.$details['#field_name'],0) == 1) { // add our js drupal_add_js(drupal_get_path('module','unlimitedfield_remove').'/unlimitedfield_remove.js'); // add our remove link $remove_button = array( '#name' => $details['#field_name'].'_'.$details['#delta'].'_remove_button', '#type' => 'markup', '#value' => t('Remove'), '#validate' => array(), '#markup' => '

Remove

', '#weight' => -99, ); $element['remove_button'] = $remove_button; } } /* * Admin settings form */ function unlimitedfield_remove_admin_form($form, &$form_state){ $fields = field_info_fields(); $form['description'] = array( '#markup' => '

Check each unlimited field you would like to add a Remove button to.

', ); foreach($fields as $key => $field){ if($field['cardinality'] == -1){ $form[$key] = array( '#type' => 'checkbox', '#title' => $key, '#default_value' => variable_get('unlimitedfield_remove_'.$key, 0), ); } } $form['submit'] = array( '#type' => 'submit', '#value' => t('Save Settings'), ); return $form; } /* * Submit functon for our settings form */ function unlimitedfield_remove_admin_form_submit($form, &$form_state){ foreach($form_state['values'] as $key => $value){ if($value == 1 || $value == 0){ variable_set('unlimitedfield_remove_'.$key, $value); } } drupal_set_message('Your settings have been saved.', 'status'); } jQuery('document').ready(function(){ jQuery('div.form-wrapper').delegate('.unlimitedfield_remove_button', 'click', function(){ // get parent table row var row = jQuery(this).closest('td').parent('tr'); // hide and empty values jQuery(row).hide(); jQuery(row).find('input').val(''); jQuery(row).find('select').val(''); jQuery(row).find('textarea').text(''); jQuery(row).find('checkbox').removeAttr('checked'); jQuery(row).find('options').removeAttr('selected'); // fix table row classes var table_id = jQuery(row).parent('tbody').parent('table').attr('id'); jQuery('#'+table_id+' tr.draggable:visible').each(function(index, element){ jQuery(element).removeClass('odd').removeClass('even'); if((index%2) == 0){ jQuery(element).addClass('odd'); } else { jQuery(element).addClass('even'); } }); }); });
Aug 19 2011
Aug 19

When the multigroup module was introduced as part of the CCK 3.x dev branch in 2009, it took CCK to a whole new level. It allowed people to easily create their own multi-value, multi-field widgets. Currently it is still only available as an alpha release for Drupal 6 though, most likely to focus more on Fields for Drupal 7 and to allow an easier upgrade path from Drupal 6 CCK 2 to Drupal 7 Fields. More information about this decision can be found on the drupal.org issue about the Status of CCK3 and plans for D7

In Drupal 7, the core Fields module doesn't offer the option to create groups, let alone multigroups. To create simple groups, you need the Field group module. It's much more powerful than its predecessor in that it allows you to not only create fieldsets, but also vertical and horizontal tabs and accordion groups.

Field group configuration

The Field group module does not cover any multigroup functionality however, and it never will. It states so on the module's project page, and more information can be found in the issue queue.

Luckily there's the Field collection module which uses the Entity API to create multi-value, multi-field widgets. The Field collection table module adds a table formatter to the collections, and Field collection also integrates with Field group to offer amazing flexibility and power. As an added bonus, both modules integrate flawlessly with Features. At this time, there is no upgrade path from D6 Multigroup to D7 Fields with Field collection though.

Jun 17 2010
Jun 17

By knowing who you are you will be directed to a page where we have displayed all our drupal services relevant to your professional identity.

Dec 09 2008
Dec 09

Following on the Data Architecture Design Sprint during a cold and snowy Chicago week in February, two Drupalcon presentations, a lot of writing, and even more debating, I am really looking forward to next week's Fields in Core code sprint. Our goal is to re-organize Drupal's content APIs and data storage around Fields instead of Nodes; think of it as "CCK in core, except for the admin UI." When the dust settles, everything will continue work just as it does now, but we will have a framework in place to allow Drupal and the community to get maximum leverage from what it is best at: Adding Value to Content.

We are assembling a team of six experienced Drupal developers at the Acquia offices in Andover MA: Yves Chedemois, Károly Négyesi, Karen Stevenson, David Strauss, Moshe Weitzman, and myself, and Dries Buytaert will be on site and participating as well. We will be tracking our progress at the Fields in Core group and expect to have plenty of tasks for virtual attendees to help with.

I'd like to particularly call attention to the fact that Yves, Karen, and Moshe are donating their personal time for this sprint. Also, NowPublic, Four Kitchens, and Acquia are donating their employees' time. Since four of the attendees need to fly in and stay in hotels, we're trying to raise $7,000 to cover the costs and maybe a nice dinner out for everyone as well. That works out to $25 per hour that these developers will be working on your favorite CMS. So, please consider making a donation. :-)

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