Issue #3025657 by seanB, dww, gbirch, benjifisher, Lendude, phenaproxima, starshaped, webchick, MrMason, dawehner: Add views area plugin to display a link to another view display within the same view

8.7.x
Gábor Hojtsy 2019-02-06 10:46:31 +01:00
parent e9792ce7e9
commit 8b4ced17a2
6 changed files with 688 additions and 0 deletions

View File

@ -77,3 +77,14 @@ views.area.http_status_code:
status_code:
type: integer
label: 'HTTP status code'
views.area.display_link:
type: views_area
label: 'Display link'
mapping:
display_id:
type: string
label: 'The display ID of the view display to link to.'
label:
type: label
label: 'The label of the link.'

View File

@ -17,3 +17,7 @@
float: left;
width: 100%;
}
/* Provide some space between display links. */
.views-display-link + .views-display-link {
margin-left: 0.5em;
}

View File

@ -0,0 +1,243 @@
<?php
namespace Drupal\views\Plugin\views\area;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\display\PathPluginBase;
/**
* Views area display_link handler.
*
* @ingroup views_area_handlers
*
* @ViewsArea("display_link")
*/
class DisplayLink extends AreaPluginBase {
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['display_id'] = ['default' => NULL];
$options['label'] = ['default' => NULL];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$allowed_displays = [];
$displays = $this->view->storage->get('display');
foreach ($displays as $display_id => $display) {
if (!$this->isPathBasedDisplay($display_id)) {
unset($displays[$display_id]);
continue;
}
$allowed_displays[$display_id] = $display['display_title'];
}
$form['description'] = [
[
'#markup' => $this->t('To make sure the results are the same when switching to the other display, it is recommended to make sure the display:'),
],
[
'#theme' => 'item_list',
'#items' => [
$this->t('Has a path.'),
$this->t('Has the same filter criteria.'),
$this->t('Has the same sort criteria.'),
$this->t('Has the same pager settings.'),
$this->t('Has the same contextual filters.'),
],
],
];
if (!$allowed_displays) {
$form['empty_message'] = [
'#markup' => '<p><em>' . $this->t('There are no path-based displays available.') . '</em></p>',
];
}
else {
$form['display_id'] = [
'#title' => $this->t('Display'),
'#type' => 'select',
'#options' => $allowed_displays,
'#default_value' => $this->options['display_id'],
'#required' => TRUE,
];
$form['label'] = [
'#title' => $this->t('Label'),
'#description' => $this->t('The text of the link.'),
'#type' => 'textfield',
'#default_value' => $this->options['label'],
'#required' => TRUE,
];
}
}
/**
* {@inheritdoc}
*/
public function validate() {
$errors = parent::validate();
// Do not add errors for the default display if it is not displayed in the
// UI.
if ($this->displayHandler->isDefaultDisplay() && !\Drupal::config('views.settings')->get('ui.show.master_display')) {
return $errors;
}
// Ajax errors can cause the plugin to be added without any settings.
$linked_display_id = !empty($this->options['display_id']) ? $this->options['display_id'] : NULL;
if (!$linked_display_id) {
$errors[] = $this->t('%current_display: The link in the %area area has no configured display.', [
'%current_display' => $this->displayHandler->display['display_title'],
'%area' => $this->areaType,
]);
return $errors;
}
// Check if the linked display hasn't been removed.
if (!$this->view->displayHandlers->get($linked_display_id)) {
$errors[] = $this->t('%current_display: The link in the %area area points to the %linked_display display which no longer exists.', [
'%current_display' => $this->displayHandler->display['display_title'],
'%area' => $this->areaType,
'%linked_display' => $this->options['display_id'],
]);
return $errors;
}
// Check if the linked display is a path-based display.
if (!$this->isPathBasedDisplay($linked_display_id)) {
$errors[] = $this->t('%current_display: The link in the %area area points to the %linked_display display which does not have a path.', [
'%current_display' => $this->displayHandler->display['display_title'],
'%area' => $this->areaType,
'%linked_display' => $this->view->displayHandlers->get($linked_display_id)->display['display_title'],
]);
return $errors;
}
// Check if options of the linked display are equal to the options of the
// current display. We "only" show a warning here, because even though we
// recommend keeping the display options equal, we do not want to enforce
// this.
$unequal_options = [
'filters' => t('Filter criteria'),
'sorts' => t('Sort criteria'),
'pager' => t('Pager'),
'arguments' => t('Contextual filters'),
];
$unequal_options = array_filter($unequal_options, function ($option) use ($linked_display_id) {
return !$this->hasEqualOptions($linked_display_id, $option);
}, ARRAY_FILTER_USE_KEY);
if ($unequal_options) {
$warning = $this->t('%current_display: The link in the %area area points to the %linked_display display which uses different settings than the %current_display display for: %unequal_options. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', [
'%current_display' => $this->displayHandler->display['display_title'],
'%area' => $this->areaType,
'%linked_display' => $this->view->displayHandlers->get($linked_display_id)->display['display_title'],
'%unequal_options' => implode(', ', $unequal_options),
]);
$this->messenger()->addWarning($warning);
}
return $errors;
}
/**
* {@inheritdoc}
*/
public function render($empty = FALSE) {
if (($empty && empty($this->options['empty'])) || empty($this->options['display_id'])) {
return [];
}
if (!$this->isPathBasedDisplay($this->options['display_id'])) {
return [];
}
// Get query parameters from the exposed input and pager.
$query = $this->view->getExposedInput();
if ($current_page = $this->view->getCurrentPage()) {
$query['page'] = $current_page;
}
// @todo Remove this parsing once these are removed from the request in
// https://www.drupal.org/node/2504709.
foreach ([
'view_name',
'view_display_id',
'view_args',
'view_path',
'view_dom_id',
'pager_element',
'view_base_path',
AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER,
FormBuilderInterface::AJAX_FORM_REQUEST,
MainContentViewSubscriber::WRAPPER_FORMAT,
] as $key) {
unset($query[$key]);
}
// Set default classes.
$classes = [
'views-display-link',
'views-display-link-' . $this->options['display_id'],
];
if ($this->options['display_id'] === $this->view->current_display) {
$classes[] = 'is-active';
}
// By default, this element sets #theme so that the 'link' theme hook is
// used for rendering, with the 'views_display_link' suffix so that themes
// can override this specifically without overriding all link theming.
return [
'#type' => 'link',
'#theme' => 'link__views_display_link',
'#title' => $this->options['label'],
'#url' => $this->view->getUrl($this->view->args, $this->options['display_id'])->setOptions(['query' => $query]),
'#options' => [
'view' => $this->view,
'target_display_id' => $this->options['display_id'],
'attributes' => ['class' => $classes],
],
];
}
/**
* Check if a views display is a path-based display.
*
* @param string $display_id
* The display ID to check.
*
* @return bool
* Whether the display ID is an allowed display or not.
*/
protected function isPathBasedDisplay($display_id) {
$loaded_display = $this->view->displayHandlers->get($display_id);
return $loaded_display instanceof PathPluginBase;
}
/**
* Check if the options of a views display are equal to the current display.
*
* @param string $display_id
* The display ID to check.
* @param string $option
* The option to check.
*
* @return bool
* Whether the option of the view display are equal to the current display
* or not.
*/
protected function hasEqualOptions($display_id, $option) {
$loaded_display = $this->view->displayHandlers->get($display_id);
return $loaded_display->getOption($option) === $this->displayHandler->getOption($option);
}
}

View File

@ -0,0 +1,418 @@
<?php
namespace Drupal\Tests\views\Kernel\Handler;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Tests the core views_handler_area_display_link handler.
*
* @group views
* @see \Drupal\views\Plugin\views\area\DisplayLink
*/
class AreaDisplayLinkTest extends ViewsKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'user', 'filter'];
/**
* {@inheritdoc}
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->installConfig(['system', 'filter']);
$this->installEntitySchema('user');
$view = Views::getView('test_view');
// Add two page displays and a block display.
$page_1 = $view->newDisplay('page', 'Page 1', 'page_1');
$page_1->setOption('path', 'page_1');
$page_2 = $view->newDisplay('page', 'Page 2', 'page_2');
$page_2->setOption('path', 'page_2');
$view->newDisplay('block', 'Block 1', 'block_1');
// Add default filter criteria, sort criteria, pager settings and contextual
// filters.
$default = $view->displayHandlers->get('default');
$default->setOption('filters', [
'status' => [
'id' => 'status',
'table' => 'views_test_data',
'field' => 'status',
'relationship' => 'none',
'operator' => '=',
'value' => 1,
],
]);
$default->setOption('sorts', [
'name' => [
'id' => 'name',
'table' => 'views_test_data',
'field' => 'name',
'relationship' => 'none',
'order' => 'ASC',
],
]);
$default->setOption('pager', [
'type' => 'mini',
'options' => ['items_per_page' => 10],
]);
$default->setOption('arguments', [
'uid' => [
'id' => 'uid',
'table' => 'views_test_data',
'field' => 'uid',
'relationship' => 'none',
],
]);
// Add display links to both page displays.
$display_links = [
'display_link_1' => [
'id' => 'display_link_1',
'table' => 'views',
'field' => 'display_link',
'display_id' => 'page_1',
'label' => 'Page 1',
'plugin_id' => 'display_link',
],
'display_link_2' => [
'id' => 'display_link_2',
'table' => 'views',
'field' => 'display_link',
'display_id' => 'page_2',
'label' => 'Page 2',
'plugin_id' => 'display_link',
],
];
$default->setOption('header', $display_links);
$view->save();
}
/**
* Tests the views area display_link handler.
*/
public function testAreaDisplayLink() {
$view = Views::getView('test_view');
// Assert only path-based displays are available in the display link
// settings form.
$view->setDisplay('page_1');
$this->assertFormOptions($view, 'display_link_1');
$this->assertFormOptions($view, 'display_link_2');
$view->setDisplay('page_2');
$this->assertFormOptions($view, 'display_link_1');
$this->assertFormOptions($view, 'display_link_2');
$view->setDisplay('block_1');
$this->assertFormOptions($view, 'display_link_1');
$this->assertFormOptions($view, 'display_link_2');
// Assert the links are rendered correctly for all displays.
$this->assertRenderedDisplayLinks($view, 'page_1');
$this->assertRenderedDisplayLinks($view, 'page_2');
$this->assertRenderedDisplayLinks($view, 'block_1');
// Assert some special request parameters are filtered from the display
// links.
$request_stack = new RequestStack();
$request_stack->push(Request::create('page_1', 'GET', [
'name' => 'John',
'sort_by' => 'created',
'sort_order' => 'ASC',
'page' => 1,
'keep' => 'keep',
'keep_another' => 1,
'view_name' => 1,
'view_display_id' => 1,
'view_args' => 1,
'view_path' => 1,
'view_dom_id' => 1,
'pager_element' => 1,
'view_base_path' => 1,
AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER => 1,
FormBuilderInterface::AJAX_FORM_REQUEST => 1,
MainContentViewSubscriber::WRAPPER_FORMAT => 1,
]));
$this->container->set('request_stack', $request_stack);
$view->destroy();
$view->setDisplay('page_1');
$view->setCurrentPage(2);
$this->executeView($view, [1]);
$this->assertSame('<a href="/page_1/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;keep=keep&amp;keep_another=1&amp;page=1" class="views-display-link views-display-link-page_1 is-active">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
$this->assertSame('<a href="/page_2/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;keep=keep&amp;keep_another=1&amp;page=1" class="views-display-link views-display-link-page_2">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
// Assert the validation adds warning messages when a display link is added
// to a display with different filter criteria, sort criteria, pager
// settings or contextual filters. Since all options are added to the
// default display there currently should be no warning messages.
$this->assertNoWarningMessages($view);
// Assert the message are shown when changing the filter criteria of page_1.
$filters = [
'name' => [
'id' => 'name',
'table' => 'views_test_data',
'field' => 'name',
'relationship' => 'none',
'operator' => '=',
'value' => '',
'exposed' => TRUE,
'expose' => [
'identifier' => 'name',
'label' => 'Name',
],
],
];
$view->displayHandlers->get('page_1')->overrideOption('filters', $filters);
$this->assertWarningMessages($view, ['filters']);
// Assert no messages are added after the default display is changed with
// the same options.
$view->displayHandlers->get('default')->overrideOption('filters', $filters);
$this->assertNoWarningMessages($view);
// Assert the message are shown when changing the sort criteria of page_1.
$sorts = [
'created' => [
'id' => 'created',
'table' => 'views_test_data',
'field' => 'created',
'relationship' => 'none',
'order' => 'DESC',
'exposed' => TRUE,
],
];
$view->displayHandlers->get('page_1')->overrideOption('sorts', $sorts);
$this->assertWarningMessages($view, ['sorts']);
// Assert no messages are added after the default display is changed with
// the same options.
$view->displayHandlers->get('default')->overrideOption('sorts', $sorts);
$this->assertNoWarningMessages($view);
// Assert the message are shown when changing the sort criteria of page_1.
$pager = [
'type' => 'full',
'options' => ['items_per_page' => 10],
];
$view->displayHandlers->get('page_1')->overrideOption('pager', $pager);
$this->assertWarningMessages($view, ['pager']);
// Assert no messages are added after the default display is changed with
// the same options.
$view->displayHandlers->get('default')->overrideOption('pager', $pager);
$this->assertNoWarningMessages($view);
// Assert the message are shown when changing the contextual filters of
// page_1.
$arguments = [
'id' => [
'id' => 'id',
'table' => 'views_test_data',
'field' => 'id',
'relationship' => 'none',
],
];
$view->displayHandlers->get('page_1')->overrideOption('arguments', $arguments);
$this->assertWarningMessages($view, ['arguments']);
// Assert no messages are added after the default display is changed with
// the same options.
$view->displayHandlers->get('default')->overrideOption('arguments', $arguments);
$this->assertNoWarningMessages($view);
// Assert an error is shown when the display ID is not set.
$display_link = [
'display_link_3' => [
'id' => 'display_link_3',
'table' => 'views',
'field' => 'display_link',
'display_id' => '',
'label' => 'Empty',
'plugin_id' => 'display_link',
],
];
$view->displayHandlers->get('page_1')->overrideOption('header', $display_link);
$view->destroy();
$view->setDisplay('page_1');
$errors = $view->validate();
$this->assertCount(1, $errors);
$this->assertCount(1, $errors['page_1']);
$this->assertSame('<em class="placeholder">Page 1</em>: The link in the <em class="placeholder">header</em> area has no configured display.', $errors['page_1'][0]->__toString());
// Assert an error is shown when linking to a display ID that doesn't exist.
$display_link['display_link_3']['display_id'] = 'non-existent';
$view->displayHandlers->get('page_1')->overrideOption('header', $display_link);
$view->destroy();
$view->setDisplay('page_1');
$errors = $view->validate();
$this->assertCount(1, $errors);
$this->assertCount(1, $errors['page_1']);
$this->assertSame('<em class="placeholder">Page 1</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">non-existent</em> display which no longer exists.', $errors['page_1'][0]->__toString());
// Assert an error is shown when linking to a display without a path.
$display_link['display_link_3']['display_id'] = 'block_1';
$view->displayHandlers->get('page_1')->overrideOption('header', $display_link);
$view->destroy();
$view->setDisplay('page_1');
$errors = $view->validate();
$this->assertCount(1, $errors);
$this->assertCount(1, $errors['page_1']);
$this->assertSame('<em class="placeholder">Page 1</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Block 1</em> display which does not have a path.', $errors['page_1'][0]->__toString());
}
/**
* Assert the display options contains only path based displays.
*
* @param \Drupal\views\ViewExecutable $view
* The view to check.
* @param string $display_link_id
* The display link ID to check the options for.
*/
protected function assertFormOptions(ViewExecutable $view, $display_link_id) {
$form = [];
$form_state = new FormState();
$view->display_handler->getHandler('header', $display_link_id)->buildOptionsForm($form, $form_state);
$this->assertTrue(isset($form['display_id']['#options']['page_1']));
$this->assertTrue(isset($form['display_id']['#options']['page_2']));
$this->assertFalse(isset($form['display_id']['#options']['block_1']));
}
/**
* Assert the display links are correctly rendered for a display.
*
* @param \Drupal\views\ViewExecutable $view
* The view to check.
* @param string $display_id
* The display ID to check the links for.
*/
protected function assertRenderedDisplayLinks(ViewExecutable $view, $display_id) {
$page_1_active = $display_id === 'page_1' ? ' is-active' : '';
$page_2_active = $display_id === 'page_2' ? ' is-active' : '';
$view->destroy();
$view->setDisplay($display_id);
$this->executeView($view);
$this->assertSame('<a href="/page_1" class="views-display-link views-display-link-page_1' . $page_1_active . '">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
$this->assertSame('<a href="/page_2" class="views-display-link views-display-link-page_2' . $page_2_active . '">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
// Assert the exposed filters, pager and contextual links are passed
// correctly in the links.
$view->destroy();
$view->setDisplay($display_id);
$view->setExposedInput([
'name' => 'John',
'sort_by' => 'created',
'sort_order' => 'ASC',
]);
$view->setCurrentPage(2);
$this->executeView($view, [1]);
$this->assertSame('<a href="/page_1/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;page=1" class="views-display-link views-display-link-page_1' . $page_1_active . '">Page 1</a>', $this->renderDisplayLink($view, 'display_link_1'));
$this->assertSame('<a href="/page_2/1?name=John&amp;sort_by=created&amp;sort_order=ASC&amp;page=1" class="views-display-link views-display-link-page_2' . $page_2_active . '">Page 2</a>', $this->renderDisplayLink($view, 'display_link_2'));
}
/**
* Render a display link.
*
* @param \Drupal\views\ViewExecutable $view
* The view to render the link for.
* @param string $display_link_id
* The display link ID to render.
*
* @return string
* The rendered display link.
*/
protected function renderDisplayLink(ViewExecutable $view, $display_link_id) {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$display_link = $view->display_handler->getHandler('header', $display_link_id)->render();
return $renderer->renderRoot($display_link)->__toString();
}
/**
* Assert no warning messages are shown when all display are equal.
*
* @param \Drupal\views\ViewExecutable $view
* The view to check.
*/
protected function assertNoWarningMessages(ViewExecutable $view) {
$messenger = $this->container->get('messenger');
$view->validate();
$this->assertCount(0, $messenger->messagesByType(MessengerInterface::TYPE_WARNING));
}
/**
* Assert the warning messages are shown after changing the page_1 display.
*
* @param \Drupal\views\ViewExecutable $view
* The view to check.
* @param array $unequal_options
* An array of options that should be unequal.
*
* @throws \Exception
*/
protected function assertWarningMessages(ViewExecutable $view, array $unequal_options) {
$messenger = $this->container->get('messenger');
// Create a list of options to check.
// @see \Drupal\views\Plugin\views\area\DisplayLink::validate()
$options = [
'filters' => 'Filter criteria',
'sorts' => 'Sort criteria',
'pager' => 'Pager',
'arguments' => 'Contextual filters',
];
// Create a list of options to check.
// @see \Drupal\views\Plugin\views\area\DisplayLink::validate()
$unequal_options_text = implode(', ', array_intersect_key($options, array_flip($unequal_options)));
$errors = $view->validate();
$messages = $messenger->messagesByType(MessengerInterface::TYPE_WARNING);
$this->assertCount(0, $errors);
$this->assertCount(3, $messages);
$this->assertSame('<em class="placeholder">Block 1</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Page 1</em> display which uses different settings than the <em class="placeholder">Block 1</em> display for: <em class="placeholder">' . $unequal_options_text . '</em>. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', $messages[0]->__toString());
$this->assertSame('<em class="placeholder">Page 1</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Page 2</em> display which uses different settings than the <em class="placeholder">Page 1</em> display for: <em class="placeholder">' . $unequal_options_text . '</em>. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', $messages[1]->__toString());
$this->assertSame('<em class="placeholder">Page 2</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Page 1</em> display which uses different settings than the <em class="placeholder">Page 2</em> display for: <em class="placeholder">' . $unequal_options_text . '</em>. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', $messages[2]->__toString());
$messenger->deleteAll();
// If the master display is shown in the UI, warnings should be shown for
// this display as well.
$this->config('views.settings')->set('ui.show.master_display', TRUE)->save();
$errors = $view->validate();
$messages = $messenger->messagesByType(MessengerInterface::TYPE_WARNING);
$this->assertCount(0, $errors);
$this->assertCount(4, $messages);
$this->assertSame('<em class="placeholder">Master</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Page 1</em> display which uses different settings than the <em class="placeholder">Master</em> display for: <em class="placeholder">' . $unequal_options_text . '</em>. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', $messages[0]->__toString());
$this->assertSame('<em class="placeholder">Block 1</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Page 1</em> display which uses different settings than the <em class="placeholder">Block 1</em> display for: <em class="placeholder">' . $unequal_options_text . '</em>. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', $messages[1]->__toString());
$this->assertSame('<em class="placeholder">Page 1</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Page 2</em> display which uses different settings than the <em class="placeholder">Page 1</em> display for: <em class="placeholder">' . $unequal_options_text . '</em>. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', $messages[2]->__toString());
$this->assertSame('<em class="placeholder">Page 2</em>: The link in the <em class="placeholder">header</em> area points to the <em class="placeholder">Page 1</em> display which uses different settings than the <em class="placeholder">Page 2</em> display for: <em class="placeholder">' . $unequal_options_text . '</em>. To make sure users see the exact same result when clicking the link, please check that the settings are the same.', $messages[3]->__toString());
$messenger->deleteAll();
$this->config('views.settings')->set('ui.show.master_display', FALSE)->save();
}
}

View File

@ -129,6 +129,14 @@ function views_views_data() {
],
];
$data['views']['display_link'] = [
'title' => t('Link to display'),
'help' => t('Displays a link to a path-based display of this view while keeping the filter criteria, sort criteria, pager settings and contextual filters.'),
'area' => [
'id' => 'display_link',
],
];
// Registers an entity area handler per entity type.
foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) {
// Excludes entity types, which cannot be rendered.

View File

@ -17,3 +17,7 @@
float: left;
width: 100%;
}
/* Provide some space between display links. */
.views-display-link + .views-display-link {
margin-left: 0.5em;
}