The new Display Suite API's

Aspilicious - @aspilicious

Prepare your environment

  • Install latest drupal 8
  • Download Display Suite 8.x-2.x-alpha10
  • It depends on the Layout Plugin module

Disable render/page caching


# if (file_exists(__DIR__ . '/settings.local.php')) {
#   include __DIR__ . '/settings.local.php';
# }
          

Who am I?

  • The co-maintainer of Display Suite since 2012
  • The maintainer of Display Suite since 2014
  • Drupal developer @nascom

What will you learn?

  • Create your own DS layout (20 minutes)
  • Create your own DS field (30 minutes)
  • Create your own DS dynamic field (25 minutes)
  • Create your own DS field template (25 minutes)

Help! I can't follow!

  • Look at the example module
  • Try to work together with your neighbours
  • Talk to me during and after this workshop
  • Try again @home

Awesome! I'm done!

  • Help your neighbours
  • Try to make something unique
  • Impress me

Create a module

In 3 steps

Create the folder

Create a directory named "montpellier" in the modules directory (root)

Create the info.yml file


type: module
name: 'Montpellier'
description: 'Display Suite test module.'
core: 8.x
package: 'Display Suite'
dependencies:
  - ds
            

Enable the module

And you're ready to go!

Create your own DS layout

In 3 steps

Define your layout

Create a file named "montpellier.layouts.yml"


montpellier:
  label: Montpellier one column layout
  category: Display Suite
  type: partial
  theme: montpellier
  #icon: images/montpellier.png
  #library: montpellier/layout
  regions:
    main:
      label: Main content
            

Create the template

Name it montpellier.html.twig
Place it in a directory named "templates"


<{{ main_wrapper }} class="montpellier-1col {{ attributes.class }} clearfix" {{ attributes }} >

{% if title_suffix.contextual_links is not null %}
{{ title_suffix.contextual_links }}
{% endif %}

{{ main }}

</{{ main_wrapper }}>
            

Enable the layout

Enjoy your victory!

Extra points

  • Create multiple regions
  • Attach a css/js library
  • Nest regions (alternative for fieldgroup)

Create your own DS field

In 7 steps

Create some directories

All the DS fields should be placed in the same folder
in order for the system to find them.

  • src (in root)
  • Plugin (in src)
  • DsField (in Plugin)

Create an empty field

It should extend DsFieldBase


/**
 * @file
 * Contains \Drupal\montpellier\Plugin\DsField\MontpellierTitle.
 */

namespace Drupal\montpellier\Plugin\DsField;

/**
 * @todo add annotation
 */
class MontpellierTitle extends DsFieldBase {

}
            

Create the plugin annotation

This is needed to "discover" the plugin


/**
 * Plugin that prefixes the title with "Montpellier"
 *
 * @DsField(
 *   id = "montpellier_title",
 *   title = @Translation("Montpellier title"),
 *   entity_type = "node",
 * )
 */
class MontpellierTitle extends DsFieldBase {

}
            

Done!

Not really...

But your field should be visible now in the interface.

Implement the build function


class MontpellierTitle extends DsFieldBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    // Fetch the entity
    $node = $this->entity();

    // Always return a render array!
    return array(
      '#markup' => $node->title->value,
      '#prefix' => 'Montpellier ',
    );
  }
}
            

Use custom settings


/**
 * {@inheritdoc}
 */
public function build() {
  $config = $this->getConfiguration();

  // Fetch the entity
  $node = $this->entity();

  // Always return a render array!
  return array(
    '#markup' => $node->title->value,
    '#prefix' => $config['prefix'] . ' ',
  );
}

/**
 * {@inheritdoc}
 */
public function settingsForm($form, FormStateInterface $form_state) {
  $config = $this->getConfiguration();

  $settings['prefix'] = array(
    '#type' => 'textfield',
    '#title' => 'Prefix',
    '#default_value' => $config['prefix'],
  );

  return $settings;
}

/**
 * {@inheritdoc}
 */
public function settingsSummary($settings) {
  $config = $this->getConfiguration();

  $summary = array();
  if (!empty($config['prefix'])) {
    $summary[] = 'Prefix: ' . $config['prefix'];
  }

  return $summary;
}

/**
 * {@inheritdoc}
 */
public function defaultConfiguration() {
  return array(
    'prefix' => 'Montpellier',
  );
}
            

Or a formatter


/**
 * {@inheritdoc}
 */
public function build() {
  $config = $this->getConfiguration();

  // Fetch formatter
  $field = $this->getFieldConfiguration();
  $formatter = $field['formatter'];

  if ($formatter == 'italic') {
    $prefix = '' . $config['prefix'] . '';
  }
  else {
    $prefix = $config['prefix'];
  }

  // Fetch the entity
  $node = $this->entity();

  // Always return a render array!
  return array(
    '#markup' => $node->title->value,
    '#prefix' => $prefix . ' ',
  );
}

/**
 * {@inheritdoc}
 */
public function formatters() {
  return array(
    'normal' => 'Normal',
    'italic' => 'Italic',
  );
}
            

Limit field in display


/**
 * {@inheritdoc}
 */
public function isAllowed() {
  if ($this->bundle() != 'article') {
    return FALSE;
  }
  else if ($this->viewMode() == 'teaser') {
    return FALSE;
  }

  return TRUE;
}
            

Create your own
DS field template

In 4 steps

Create some directories

All the DS field templates should be placed in the same folder in order for the system to find them.

  • src (in root)
  • Plugin (in src)
  • DsFieldTemplate (in Plugin)

Create an empty field layout class

It should extend DsFieldLayoutBase


/**
 * @file
 * Contains \Drupal\montpellier\Plugin\DsFieldLayout\Montpellier.
 */

namespace Drupal\montpellier\Plugin\DsFieldLayout;

use Drupal\ds\Plugin\DsFieldLayout\DsFieldLayoutBase;

/**
 * @todo create the annotation
 */
class Montpellier extends DsFieldLayoutBase {
}
            

Create the plugin annotation

This is needed to "discover" the layout


/**
 * @DsFieldLayout(
 *   id = "montpellier",
 *   title = @Translation("Montpellier"),
 *   theme = "theme_ds_field_montpellier",
 * )
 */
class Montpellier extends DsFieldLayoutBase {
}
            

Implement the theme function


/**
 * Wraps the output in a montpellier class
 */
function theme_ds_field_montpellier($variables) {
  $output = '';

  // Render the label.
  if (!$variables['label_hidden']) {
    $output .= '
' . $variables['label']; if (\Drupal::config('ds.settings')->get('ft-show-colon')) { $output .= ':'; } $output .= '
'; } // Render the items. foreach ($variables['items'] as $item) { $output .= drupal_render($item['content']); } return '
' . $output .'
'; }

Create your own
DS *dynamic* field

In 7 steps

Create some directories

All the DS dynamic fields need a form.

  • src (in root)
  • Forms (in src)

Create the form

It should extend FieldFormBase


/**
 * @file
 * Contains \Drupal\montpellier\Form\MontpellierFieldForm.
 */

namespace Drupal\montpellier\Form;

use Drupal\Core\Form\FormStateInterface;
use Drupal\ds\Form\FieldFormBase;

/**
 * Configures montpellier fields.
 */
class MontpellierFieldForm extends FieldFormBase {

  /**
   * The type of the dynamic ds field
   */
  const TYPE = 'montpellier';

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'ds_field_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, $field_key = '') {
    $form = parent::buildForm($form, $form_state, $field_key);
    $field = $this->field;

    $form['content'] = array(
      '#type' => 'text_format',
      '#title' => t('Field content'),
      '#default_value' => isset($field['properties']['content']['value']) ? $field['properties']['content']['value'] : '',
      '#format' => isset($field['properties']['content']['format']) ? $field['properties']['content']['format'] : 'plain_text',
      '#base_type' => 'textarea',
      '#required' => TRUE,
    );

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function getProperties(FormStateInterface $form_state) {
    return array(
      'content' => $form_state->getValue('content'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getType() {
    return MontpellierFieldForm::TYPE;
  }

  /**
   * {@inheritdoc}
   */
  public function getTypeLabel() {
    return 'Montpellier field';
  }

}
            

Create routes for this form

Placed in a routing.yml file


ds.manage_montpellier_field:
  path: 'admin/structure/ds/fields/manage_montpellier/{field_key}'
  defaults:
    _form: '\Drupal\montpellier\Form\MontpellierFieldForm'
    _title: 'Edit a montpellier field'
  requirements:
    _permission: 'admin fields'
ds.add_montpellier_field:
  path: 'admin/structure/ds/fields/manage_montpellier'
  defaults:
    _form: '\Drupal\montpellier\Form\MontpellierFieldForm'
    _title: 'Add a montpellier field'
  requirements:
    _permission: 'admin fields'
                

Create a local action

Placed in a links.action.yml file


ds.montpellier_field_add_local_action:
  route_name: ds.add_montpellier_field
  title: 'Add a montpellier field'
  appears_on:
    - ds.fields_list
                

Create some directories

This needed for the magic to happen

  • src (in root)
  • Plugin (in src)
  • Derivative (in Plugin)

Create the derivative

It has to extend the DynamicField class of DS


/**
 * @file
 * Contains \Drupal\montpellier\Plugin\Derivative\DynamicMontpellierField.
 */

namespace Drupal\montpellier\Plugin\Derivative;

use Drupal\montpellier\Form\MontpellierFieldForm;
use Drupal\ds\Plugin\Derivative\DynamicField;

/**
 * Retrieves dynamic code field plugin definitions.
 */
class DynamicMontpellierField extends DynamicField {

  /**
   * {@inheritdoc}
   */
  protected function getType() {
    return MontpellierFieldForm::TYPE;
  }

}
                

Create the actual DS field


/**
 * @file
 * Contains \Drupal\montpellier\Plugin\DsField\DynamicMontpellierField.
 */

namespace Drupal\montpellier\Plugin\DsField;

use Drupal\ds\Plugin\DsField\DsFieldBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Defines a generic dynamic montpellier field.
 *
 * @DsField(
 *   id = "dynamic_montpellier_field",
 *   deriver = "Drupal\montpellier\Plugin\Derivative\DynamicMontpellierField"
 * )
 */
class DynamicMontpellierField extends DsFieldBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $content = $this->content();
    $format = $this->format();

    return array(
      '#type' => 'processed_text',
      '#text' => $content,
      '#format' => $format,
      '#filter_types_to_skip' => array(),
      '#langcode' => '',
    );
  }

  /**
   * {@inheritdoc}
   */
  public function content() {
    $definition = $this->getPluginDefinition();
    return $definition['properties']['content']['value'];
  }

  /**
   * {@inheritdoc}
   */
  public function format() {
    $definition = $this->getPluginDefinition();
    return $definition['properties']['content']['format'];
  }

  /**
   * {@inheritdoc}
   */
  public function isAllowed() {
    $definition = $this->getPluginDefinition();
    return DsFieldBase::dynamicFieldIsAllowed($definition, $this->bundle(), $this->viewMode());
  }

}
                

Important to know

  • API is not yet 100% stable
  • Don't forget to think about caching

The end!

Got questions?