Skip to content
Snippets Groups Projects
Unverified Commit 6aed2e94 authored by Lauri Timmanee's avatar Lauri Timmanee
Browse files

Issue #3151553 by mherchel, mandclu, danflanagan8, catch, Lendude, Gábor...

Issue #3151553 by mherchel, mandclu, danflanagan8, catch, Lendude, Gábor Hojtsy:  Create new “Views Responsive Grid” format for Views Core
parent baf3e893
Branches
Tags
38 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4289Issue #1344552 by marcingy, Niklas Fiekas, Ravi.J, aleevas, Eduardo Morales...,!4114Issue #2707291: Disable body-level scrolling when a dialog is open as a modal,!4100Issue #3249600: Add support for PHP 8.1 Enums as allowed values for list_* data types,!3630Issue #2815301 by Chi, DanielVeza, kostyashupenko, smustgrave: Allow to create...,!3600Issue #3344629: Passing null to parameter #1 ($haystack) of type string is deprecated,!3291Issue #3336463: Rewrite rules for gzipped CSS and JavaScript aggregates never match,!3102Issue #3164428 by DonAtt, longwave, sahil.goyal, Anchal_gupta, alexpott: Use...,!2853#3274419 Makes BaseFieldOverride inherit the internal property from the base field.,!2661Issue #3295972 by Munavijayalakshmi, nitin_lama, arunkumark, cilefen,...,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2074Issue #2707689: NodeForm::actions() checks for delete access on new entities,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1484Exposed filters get values from URL when Ajax is on,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1254Issue #3238915: Refactor (if feasible) uses of the jQuery ready function to use VanillaJS,!1162Issue #3100350: Unable to save '/' root path alias,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!957Added throwing of InvalidPluginDefinitionException from getDefinition().,!925Issue #2339235: Remove taxonomy hard dependency on node module,!877Issue #2708101: Default value for link text is not saved,!873Issue #2875228: Site install not using batch API service,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links,!844Resolve #3036010 "Updaters",!712Issue #2909128: Autocomplete intermittent on Chrome Android,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
Showing
with 418 additions and 1 deletion
......@@ -48,6 +48,23 @@ views.style.grid:
type: boolean
label: 'Default views column classes'
views.style.grid_responsive:
type: views_style
label: 'Grid - Responsive'
mapping:
columns:
type: integer
label: 'Maximum number of columns'
cell_min_width:
type: integer
label: 'Minimum cell width'
grid_gutter:
type: integer
label: 'Grid gutter'
alignment:
type: string
label: 'Alignment'
views.style.table:
type: views_style
label: 'Table'
......
/**
* CSS for Views responsive grid style.
*/
.views-view-responsive-grid {
--views-responsive-grid--layout-gap: 10px; /* Will be overridden by an inline style. */
--views-responsive-grid--column-count: 4; /* Will be overridden by an inline style. */
--views-responsive-grid--cell-min-width: 100px; /* Will be overridden by an inline style. */
}
.views-view-responsive-grid--horizontal {
/**
* Calculated values.
*/
--views-responsive-grid--gap-count: calc(var(--views-responsive-grid--column-count) - 1);
--views-responsive-grid--total-gap-width: calc(var(--views-responsive-grid--gap-count) * var(--views-responsive-grid--layout-gap));
--views-responsive-grid-item--max-width: calc((100% - var(--views-responsive-grid--total-gap-width)) / var(--views-responsive-grid--column-count));
display: grid;
grid-template-columns: repeat(auto-fill, minmax(max(var(--views-responsive-grid--cell-min-width), var(--views-responsive-grid-item--max-width)), 1fr));
gap: var(--views-responsive-grid--layout-gap);
}
.views-view-responsive-grid--vertical {
margin-bottom: calc(var(--views-responsive-grid--layout-gap) * -1); /* Offset the bottom row's padding. */
column-width: var(--views-responsive-grid--cell-min-width);
column-count: var(--views-responsive-grid--column-count);
column-gap: var(--views-responsive-grid--layout-gap);
}
.views-view-responsive-grid--vertical .views-view-responsive-grid__item > * {
padding-bottom: var(--views-responsive-grid--layout-gap);
page-break-inside: avoid;
break-inside: avoid;
}
<?php
namespace Drupal\views\Plugin\views\style;
use Drupal\Core\Form\FormStateInterface;
/**
* Style plugin to render each item in a responsive grid cell.
*
* @ingroup views_style_plugins
*
* @ViewsStyle(
* id = "grid_responsive",
* title = @Translation("Responsive Grid"),
* help = @Translation("Displays rows in a responsive grid."),
* theme = "views_view_grid_responsive",
* display_types = {"normal"}
* )
*/
class GridResponsive extends StylePluginBase {
/**
* {@inheritdoc}
*/
protected $usesRowPlugin = TRUE;
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['columns'] = ['default' => '4'];
$options['cell_min_width'] = ['default' => '100'];
$options['grid_gutter'] = ['default' => '10'];
$options['alignment'] = ['default' => 'horizontal'];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['columns'] = [
'#type' => 'number',
'#title' => $this->t('Maximum number of columns'),
'#attributes' => ['style' => 'width: 6em;'],
'#description' => $this->t('The maximum number of columns that will be displayed within the grid.'),
'#default_value' => $this->options['columns'],
'#required' => TRUE,
'#min' => 1,
];
$form['cell_min_width'] = [
'#type' => 'number',
'#title' => $this->t('Minimum grid cell width'),
'#field_suffix' => 'px',
'#attributes' => ['style' => 'width: 6em;'],
'#description' => $this->t('The minimum width of the grid cells. If the grid container becomes narrow, the grid cells will reflow onto the next row as needed.'),
'#default_value' => $this->options['cell_min_width'],
'#required' => TRUE,
'#min' => 1,
];
$form['grid_gutter'] = [
'#type' => 'number',
'#title' => $this->t('Grid gutter spacing'),
'#field_suffix' => 'px',
'#attributes' => ['style' => 'width: 6em;'],
'#description' => $this->t('The spacing between the grid cells.'),
'#default_value' => $this->options['grid_gutter'],
'#required' => TRUE,
'#min' => 0,
];
$form['alignment'] = [
'#type' => 'radios',
'#title' => $this->t('Alignment'),
'#options' => ['horizontal' => $this->t('Horizontal'), 'vertical' => $this->t('Vertical')],
'#default_value' => $this->options['alignment'],
'#description' => $this->t('Horizontal alignment will place items starting in the upper left and moving right. Vertical alignment will place items starting in the upper left and moving down.'),
];
}
}
{#
/**
* @file
* Default theme implementation for views to display rows in a responsive grid.
*
* Available variables:
* - attributes: HTML attributes for the wrapping element.
* - title: The title of this group of rows.
* - view: The view object.
* - rows: The rows contained in this view.
* - options: The view plugin style options.
* - alignment: a string set to either 'horizontal' or 'vertical'.
* - columns: A number representing the max number of columns.
* - cell_min_width: A number representing the minimum width of the grid cell.
* - grid_gutter: A number representing the space between the grid cells.
* - items: A list of grid items.
* - attributes: HTML attributes for each row or column.
* - content: A list of columns or rows. Each row or column contains:
* - attributes: HTML attributes for each row or column.
* - content: The row or column contents.
*
* @see template_preprocess_views_view_grid_responsive()
*
* @ingroup themeable
*/
#}
{{ attach_library('views/views.responsive-grid') }}
{%
set classes = [
'views-view-responsive-grid',
'views-view-responsive-grid--' ~ options.alignment,
]
%}
{% set responsive_grid_styles = [
'--views-responsive-grid--column-count:' ~ options.columns ~ ';',
'--views-responsive-grid--cell-min-width:' ~ options.cell_min_width ~ 'px;',
'--views-responsive-grid--layout-gap:' ~ options.grid_gutter ~ 'px;',
]
%}
{% if title %}
<h3>{{ title }}</h3>
{% endif %}
<div{{ attributes.addClass(classes).setAttribute('style', responsive_grid_styles|join()) }}>
{% for item in items %}
<div class="views-view-responsive-grid__item">
<div class="views-view-responsive-grid__item-inner">
{{- item.content -}}
</div>
</div>
{% endfor %}
</div>
langcode: en
status: true
dependencies: { }
id: test_grid_responsive
label: ''
module: views
description: ''
tag: ''
base_table: views_test_data
base_field: nid
display:
default:
display_options:
defaults:
fields: false
pager: false
sorts: false
fields:
age:
field: age
id: age
relationship: none
table: views_test_data
plugin_id: numeric
id:
field: id
id: id
relationship: none
table: views_test_data
plugin_id: numeric
name:
field: name
id: name
relationship: none
table: views_test_data
plugin_id: string
pager:
options:
offset: 0
type: none
sorts:
id:
field: id
id: id
order: ASC
relationship: none
table: views_test_data
plugin_id: numeric
style:
type: grid_responsive
options:
grouping: { }
row:
type: fields
display_plugin: default
display_title: Default
id: default
position: 0
page_1:
display_options:
path: test-grid
display_plugin: page
display_title: 'Page display'
id: page_1
position: 1
<?php
namespace Drupal\Tests\views\Kernel\Plugin;
use Drupal\views\Views;
/**
* Tests the grid_responsive style plugin.
*
* @group views
* @see \Drupal\views\Plugin\views\style\GridResponsive
*/
class StyleGridResponsiveTest extends PluginKernelTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_grid_responsive'];
/**
* Generates a grid_responsive and asserts that it is displaying correctly.
*
* @param array $options
* Options for the style plugin.
* @param array $expected
* Expected values sued for assertions.
*
* @dataProvider providerTestResponsiveGrid
*/
public function testResponsiveGrid(array $options, array $expected): void {
// Create and preview a View with the provided options.
$view = Views::getView('test_grid_responsive');
$view->setDisplay('default');
$view->initStyle();
$view->initHandlers();
$view->initQuery();
$view->style_plugin->options = $options + $view->style_plugin->options;
$this->executeView($view);
$output = $view->preview();
$output = \Drupal::service('renderer')->renderRoot($output);
$this->setRawContent($output);
// Confirm that the alignment class is added.
$result = $this->xpath('//div[contains(@class, "views-view-responsive-grid") and contains(@class, :alignment)]', [':alignment' => 'views-view-responsive-grid--' . $expected['alignment']]);
$this->assertGreaterThan(0, count($result), "Alignment CSS variable value is detected and correct.");
// Check for CSS variables in style attribute.
$result = $this->xpath('//div[contains(@class, "views-view-responsive-grid") and contains(@style, :columns)]', [':columns' => '--views-responsive-grid--column-count:' . $expected['columns']]);
$this->assertGreaterThan(0, count($result), "Max-columns CSS variable value is detected and correct.");
$result = $this->xpath('//div[contains(@class, "views-view-responsive-grid") and contains(@style, :min-width)]', [':min-width' => '--views-responsive-grid--cell-min-width:' . $expected['cell_min_width'] . 'px']);
$this->assertGreaterThan(0, count($result), "Min-width CSS variable value is detected and correct.");
$result = $this->xpath('//div[contains(@class, "views-view-responsive-grid") and contains(@style, :gutter)]', [':gutter' => '--views-responsive-grid--layout-gap:' . $expected['grid_gutter'] . 'px']);
$this->assertGreaterThan(0, count($result), "Gutter CSS variable value is detected and correct.");
// Assert that the correct number of elements have been rendered and that
// markup structure is correct.
$result = $this->xpath('//div[contains(@class, "views-view-responsive-grid")]/div[contains(@class, "views-view-responsive-grid__item")]/div[contains(@class, "views-view-responsive-grid__item-inner")]');
// There are five results for this test view. See ViewTestData::dataSet().
$expected_count = 5;
$this->assertSame($expected_count, count($result), "The expected number of items are rendered in the correct structure.");
}
/**
* Data provider for testing various configurations.
*
* @return array
* Array containing options for the style plugin and expected values.
*/
public function providerTestResponsiveGrid() {
return [
'horizontal' => [
'settings' => [
'columns' => 7,
'cell_min_width' => 123,
'grid_gutter' => 13,
'alignment' => 'horizontal',
],
'expected' => [
'columns' => 7,
'cell_min_width' => 123,
'grid_gutter' => 13,
'alignment' => 'horizontal',
],
],
'vertical' => [
'settings' => [
'columns' => 8,
'cell_min_width' => 50,
'grid_gutter' => 44,
'alignment' => 'vertical',
],
'expected' => [
'columns' => 8,
'cell_min_width' => 50,
'grid_gutter' => 44,
'alignment' => 'vertical',
],
],
'default options' => [
'settings' => [],
'expected' => [
'columns' => 4,
'cell_min_width' => 100,
'grid_gutter' => 10,
'alignment' => 'horizontal',
],
],
];
}
}
......@@ -16,3 +16,9 @@ views.ajax:
- core/once
- core/internal.jquery.form
- core/drupal.ajax
views.responsive-grid:
version: VERSION
css:
layout:
css/views-responsive-grid.css: {}
......@@ -794,6 +794,37 @@ function template_preprocess_views_view_grid(&$variables) {
$variables['items'] = $items;
}
/**
* Prepares variables for views grid - responsive style templates.
*
* Default template: views-view-grid-responsive.html.twig.
*
* @param array $variables
* An associative array containing:
* - view: The view object.
* - rows: An array of row items. Each row is an array of content.
*/
function template_preprocess_views_view_grid_responsive(&$variables) {
$variables['options'] = $variables['view']->style_plugin->options;
$view = $variables['view'];
$items = [];
foreach ($variables['rows'] as $id => $item) {
$attribute = new Attribute();
if ($row_class = $view->style_plugin->getRowClass($id)) {
$attribute->addClass($row_class);
}
$items[$id] = [
'content' => $item,
'attributes' => $attribute,
];
}
$variables['items'] = $items;
}
/**
* Prepares variables for views unformatted rows templates.
*
......
......@@ -9,6 +9,15 @@
*/
class Stable9LibraryOverrideTest extends StableLibraryOverrideTestBase {
/**
* A list of libraries to skip checking, in the format extension/library_name.
*
* @var string[]
*/
protected $librariesToSkip = [
'views/views.responsive-grid',
];
/**
* {@inheritdoc}
*/
......
......@@ -27,6 +27,7 @@ class Stable9TemplateOverrideTest extends KernelTestBase {
// Registered as a template in the views_theme() function in views.module
// but an actual template does not exist.
'views-form-views-form',
'views-view-grid-responsive',
];
/**
......
......@@ -42,7 +42,9 @@ class StableLibraryOverrideTest extends StableLibraryOverrideTestBase {
*
* @var string[]
*/
protected $librariesToSkip = [];
protected $librariesToSkip = [
'views/views.responsive-grid',
];
/**
* {@inheritdoc}
......
......@@ -25,6 +25,7 @@ class StableTemplateOverrideTest extends KernelTestBase {
*/
protected $templatesToSkip = [
'views-form-views-form',
'views-view-grid-responsive',
];
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment