Custom form displays

We needed to have simple field-level permissions on entities. Our project model uses nested entities. Students have a project that is made of multiple project steps. Projects have deadlines associated with each step, and these are editable by teachers but not students.

Drupal 8 offers a new feature "entity form modes" that parallels the more familiar "entity display modes" (previously called view modes). How to use these form modes was not clear, though. Defining the form mode is easy: just configure a new form mode at /admin/structure/display-modes/form. Using them was more complicated.

We have a deadline form mode for both projects and project steps.

Inline Entity Form makes it simple to use custom form modes for the entity reference fields. The entity reference fields on project nodes can be presented on a form using the inline entity form widget, which will allow the project step nodes to be edited using their deadline form mode.

But for the top-level node (project) to be edited in a different form mode, you have to implement hook_entity_type_build and specify the new form mode as an operation.

/**
 * Implements hook_entity_type_build().
 * @param array $entity_types
 */
function cafprojectedit_entity_type_build(array &$entity_types) {
  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  $entity_types['node']->setFormClass('deadline', \Drupal\node\NodeForm::class);
}

Then in your routing file, you use that entity form mode or operation name as the default for _entity_form.

cafprojectedit.project.deadlines:
  path: '/node/{node}/deadlines'
  defaults:
    _entity_form: node.deadline
    _title: 'Deadlines'
  requirements:
    _custom_access: '\Drupal\cafprojectedit\DeadlineAccess::access'
  options:
    _admin_route: true

Using form modes allowed us to segregate the fields that teachers should edit from the fields students can edit. The fields don't have special permissions, but the forms where those fields appear have permissions on their route.

Calculated Fields

Drupal 8 has a very useful feature for developers to support calculated properties on fields and entities. On DiscoverDesign, we use this feature as a convenient way to encapsulate different mannertune of reverse-lookups for entity reference fields.

The documentation for the computed field property explains how this feature works. It's crucial to appreciate that the computed property applies to properties on fields and entities and not to whole fields. For sure, this was our greatest stumbling block as we implemented the reverse half of our entity reference fields.

The case of adding a computed property on a field is plainly covered in the documentation linked above. The Drupal core example is the processed property on formatted text fields.

However, the case of a purely computed field attached to an entity, especially a specific bundle, is not demonstrated in Drupal core. For a simple reverse-reference field, we have this example:

/**
 * Implement hook_entity_bundle_field_info().
 *
 * @param EntityTypeInterface $entity_type
 * @param $bundle
 * @param array $base_field_definitions
 * @return array
 */
function cafprojectedit_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields = array();
  if ($entity_type->id() == 'node' && ($bundle === 'project_step' || $bundle === 'project_step_no_media')) {

    // A basic back-reference of the project step refernece field.
    $fields['project'] = \Drupal\Core\Field\BaseFieldDefinition::create('entity_reference')
      ->setCardinality(1)
      ->setLabel(t('Project'))
      ->setComputed(TRUE)
      ->setClass(\Drupal\cafprojectedit\ProjectBackReferenceItemList::class)
      ->setSetting('back reference', ['node', 'project', 'field_project_step'])
    ;
  }
  return $fields;
}

The class ProjectBackReferenceItemList is a simple extension of Drupal core's EntityReferenceFieldItemList class with significant methods wrapped in a check to actually do the calculation. In the compute method in this example, you could do anything and return an array of all the values for the field. We found it was most practical to imitate the structure of the core entity reference field.

<?php

namespace Drupal\cafprojectedit;

use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldItemList;

class ProjectBackReferenceItemList extends EntityReferenceFieldItemList
{
  private $computed = false;

  public function getValue($include_computed = FALSE) {
    if (!$this->computed) {
      $this->compute();
      $this->computed = true;
    }
    return parent::getValue($include_computed);
  }

  public function isEmpty() {
    if (!$this->computed) {
      $this->compute();
      $this->computed = true;
    }
    return parent::isEmpty();
  }

  public function referencedEntities() {
    if (!$this->computed) {
      $this->compute();
      $this->computed = true;
    }
    return parent::referencedEntities();
  }

  protected function compute() {
    /** @var FieldableEntityInterface $entity */
    $entity = $this->getEntity();

    list($entityTypeId, $bundle, $fieldName) = $this->getSetting('back reference');

    $query = \Drupal::entityQuery($entityTypeId)->condition($fieldName, $entity->id());
    $values = $query->execute();
    array_walk($values, [$this, 'appendItem']);
  }
}

We override isEmpty so that the value can be serialized as well. This much work only gives you the ability to use the field in your module and theme code. If you actually want to render the field using Drupal's field rendering system, you will need to at least implement hook_entity_extra_field_info to expose the field to the display setting of your entity configuration and hook_node_view_alter (for nodes) to actually render the field as part of your entity. Even with that, you will not have your typical field formatter settings, but this is still an incredibly useful feature for Drupal developers.

Build a Custom Drupal Site

Are you considering a partner in helping develop a custom Drupal website for your business? Contact us! We’re experts in delivering custom Drupal development for small businesses and large enterprises - and we’re eager to speak with you.

Caxy Interactive is a Custom Software Development Company

See what other services Caxy has to offer.