TextWithExpandCollapseButtonsFormatter.php in Formatter Suite 8
File
src/Plugin/Field/FieldFormatter/TextWithExpandCollapseButtonsFormatter.phpView source
<?php
namespace Drupal\formatter_suite\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\formatter_suite\Branding;
/**
* Formats text with expand/collapse buttons to show more/less.
*
* Long text fields may have long text. When presented along with other
* content on the same page, the text can be overwhelming and make it hard
* to find the other content. This formatter temporarily shortens the long
* text to a specified height and adds an "Expand" button. Clicking the
* button expands the text display to full size, and adds a "Collapse"
* button. Clicking that button shortes the text again.
*
* @FieldFormatter(
* id = "formatter_suite_text_with_expand_collapse_buttons",
* label = @Translation("Formatter Suite - Text with expand/collapse buttons"),
* weight = 1000,
* field_types = {
* "text",
* "text_long",
* "text_with_summary",
* "string_long"
* }
* )
*/
class TextWithExpandCollapseButtonsFormatter extends FormatterBase {
/*---------------------------------------------------------------------
*
* Configuration.
*
*---------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'collapsedHeight' => '8em',
'expandButtonLabel' => t('Expand...'),
'collapseButtonLabel' => t('Collapse...'),
'animationDuration' => 500,
];
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
// Get current settings.
$collapsedHeight = $this
->getSetting('collapsedHeight');
$animationDuration = $this
->getSetting('animationDuration');
// Security: The animation duration is entered by an administrator.
// It should be a simple integer, with no other characters, HTML, or
// HTML entities.
//
// By parsing it as an integer, we ignore anything else and remove
// any security issues.
$animationDuration = intval($animationDuration);
// Security: The collapse height is entered by an administrator.
// It should be a number followed by CSS units, such as "px", "pt",
// or "em". It should not contain HTML or HTML entities.
//
// If integer parsing of the string yields a zero, then the string
// is assumed to be empty or invalid and collapsing is disabled.
// Otherwise the string is santized using an Html escape filter
// that escapes all HTML and HTML entities. If the admin enters these,
// the resulting string is not likely to work as a collapse height
// and the Javascript will not get a meaningful result, but it will
// still be safe.
$collapsedHeight = Html::escape($collapsedHeight);
$hasCollapseHeight = TRUE;
if (empty($collapsedHeight) === TRUE || $collapsedHeight === "0" || (int) $collapsedHeight === 0) {
$hasCollapseHeight = FALSE;
}
// Present.
$summary = parent::settingsSummary();
if ($hasCollapseHeight === FALSE) {
$summary[] = $this
->t('Disabled because no collapsed height set.');
}
else {
$summary[] = $this
->t('Shorten long text areas to @collapsedHeight.', [
'@collapsedHeight' => $collapsedHeight,
]);
if ($animationDuration > 0) {
$summary[] = $this
->t('Animate over @animationDuration milliseconds.', [
'@animationDuration' => $animationDuration,
]);
}
}
return $summary;
}
/*---------------------------------------------------------------------
*
* Settings form.
*
*---------------------------------------------------------------------*/
/**
* Returns a brief description of the formatter.
*
* @return string
* Returns a brief translated description of the formatter.
*/
protected function getDescription() {
return $this
->t('Present long text in a shortened area and include links to expand the text to full height, and collapse it back again.');
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $formState) {
//
// Start with the parent form.
$elements = parent::settingsForm($form, $formState);
$elements['#attached'] = [
'library' => [
'formatter_suite/formatter_suite.settings',
],
];
// Add branding.
$elements = [];
$elements = Branding::addFieldFormatterBranding($elements);
$elements['#attached']['library'][] = 'formatter_suite/formatter_suite.fieldformatter';
$elements['description'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => $this
->getDescription(),
'#weight' => -1000,
'#attributes' => [
'class' => [
'formatter_suite-settings-description',
],
],
];
// Add each of the values.
$elements['collapsedHeight'] = [
'#type' => 'textfield',
'#title' => $this
->t('Collapsed height'),
'#size' => 10,
'#default_value' => $this
->getSetting('collapsedHeight'),
'#description' => $this
->t("Text height when collapsed. Use CSS units (e.g. '200px', '40pt', '8em'). Empty or zero value disables."),
'#attributes' => [
'autocomplete' => 'off',
'autocapitalize' => 'none',
'spellcheck' => 'false',
'autocorrect' => 'off',
],
];
$elements['collapseButtonLabel'] = [
'#type' => 'textfield',
'#title' => $this
->t('Collapse link label'),
'#size' => 10,
'#maxlength' => 128,
'#default_value' => $this
->getSetting('collapseButtonLabel'),
];
$elements['expandButtonLabel'] = [
'#type' => 'textfield',
'#title' => $this
->t('Expand link label'),
'#size' => 10,
'#maxlength' => 128,
'#default_value' => $this
->getSetting('expandButtonLabel'),
];
$elements['animationDuration'] = [
'#type' => 'number',
'#title' => $this
->t('Animation duration'),
'#size' => 10,
'#default_value' => $this
->getSetting('animationDuration'),
'#description' => $this
->t('Animation time in milliseconds (e.g. 500 = 1/2 second). Empty or zero value disables animation.'),
];
return $elements;
}
/*---------------------------------------------------------------------
*
* View.
*
*---------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langCode) {
//
// The $items array has a list of items to format. We need to return
// an array with identical indexing and corresponding render elements
// for those items.
if ($items
->isEmpty() === TRUE) {
return [];
}
// Get current settings.
$collapsedHeight = $this
->getSetting('collapsedHeight');
$animationDuration = $this
->getSetting('animationDuration');
$collapseButtonLabel = $this
->getSetting('collapseButtonLabel');
$expandButtonLabel = $this
->getSetting('expandButtonLabel');
// Security: The button labels are entered by an administrator.
// They may legitimately include HTML entities and minor HTML, but
// they should not include dangerous HTML. For instance, it is
// legitimate to include <span class="blah"> to style the label,
// or to include <img src="blah"> to add an icon image. However,
// it is not legitimate to add <style> or <script>.
//
// So, Xss::filterAdmin() is used here to get rid of the most
// dangerous HTML, like <style> and <script>.
//
// We'd like the admin-entered text to be translated, if a site
// is using that Drupal feature. However, simply calling t() will
// not quite work. Drupal.org builds translatable text tables by
// scanning the source code for calls to t() with literal strings.
// Since the admin-entered button label text here is not a literal
// string, it will not be found by that scan. This means it will
// not be in automatically-generated translation tables and t()
// will not necessarily do anything.
//
// Calling t() anyway will still do the translation table lookup.
// If a site has MANUALLY entered the text into their own translation
// tables, then translation will take place. Otherwise the text will
// be used as-is.
//
// We'd like to call $this->t() here to do the translation, but the
// various Drupal style checkers complain, even though this is a
// legitimate use. To avoid those complaints, we get the string
// translator from the StringTranslationTrait included in the
// FormatterBase parent class. Calling the translate() method
// directly dodges the style checkers.
$translator = $this
->getStringTranslation();
$collapseButtonLabel = $translator
->translate(Xss::filterAdmin($collapseButtonLabel));
$expandButtonLabel = $translator
->translate(Xss::filterAdmin($expandButtonLabel));
// Security: The animation duration is entered by an administrator.
// It should be a simple integer, with no other characters, HTML, or
// HTML entities.
//
// By parsing it as an integer, we ignore anything else and remove
// any security issues.
$animationDuration = intval($animationDuration);
// Security: The collapse height is entered by an administrator.
// It should be a number followed by CSS units, such as "px", "pt",
// or "em". It should not contain HTML or HTML entities.
//
// If integer parsing of the string yields a zero, then the string
// is assumed to be empty or invalid and collapsing is disabled.
// Otherwise the string is santized using an Html escape filter
// that escapes all HTML and HTML entities. If the admin enters these,
// the resulting string is not likely to work as a collapse height
// and the Javascript will not get a meaningful result, but it will
// still be safe.
$collapsedHeight = Html::escape($collapsedHeight);
$hasCollapsedHeight = TRUE;
if (empty($collapsedHeight) === TRUE || $collapsedHeight === "0" || (int) $collapsedHeight === 0) {
$hasCollapsedHeight = FALSE;
}
// If there is no collapsed height, show text full height.
$build = [];
if ($hasCollapsedHeight === FALSE) {
foreach ($items as $delta => $item) {
$build[$delta] = [
'#type' => 'processed_text',
'#text' => $item->value,
'#format' => $item->format,
'#langcode' => $item
->getLangcode(),
];
}
return $build;
}
// Nest the text, add buttons, and add a behavior script.
foreach ($items as $delta => $item) {
$build[$delta] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'formatter_suite-text-with-expand-collapse-buttons',
],
],
'#attached' => [
'library' => [
'formatter_suite/formatter_suite.usage',
'formatter_suite/formatter_suite.text_with_expand_collapse_buttons',
],
],
'text' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'formatter_suite-text',
],
'data-formatter_suite-collapsed-height' => $collapsedHeight,
'data-formatter_suite-animation-duration' => $animationDuration,
],
'processedtext' => [
'#type' => 'processed_text',
'#text' => $item->value,
'#format' => $item->format,
'#langcode' => $item
->getLangcode(),
],
],
'collapse' => [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => '<a href="#">' . $collapseButtonLabel . '</a>',
'#attributes' => [
'class' => [
'formatter_suite-text-collapse-button',
],
'style' => 'display: none',
],
],
'expand' => [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => '<a href="#">' . $expandButtonLabel . '</a>',
'#attributes' => [
'class' => [
'formatter_suite-text-expand-button',
],
'style' => 'display: none',
],
],
];
}
return $build;
}
}
Classes
Name | Description |
---|---|
TextWithExpandCollapseButtonsFormatter | Formats text with expand/collapse buttons to show more/less. |