From a3bcf878f69407265c3fe9872712e0b30aa985e3 Mon Sep 17 00:00:00 2001 From: Mark Carver Date: Mon, 15 Jul 2013 21:48:51 -0500 Subject: Issue #1903746 by Mark Carver, mgifford, tkoleary, DaneMacaulay: Replace the views grid table template with one using divs --- .../views/config/schema/views.style.schema.yml | 17 +- core/modules/views/css/views.module.css | 10 ++ .../lib/Drupal/views/Plugin/views/style/Grid.php | 64 +++++--- .../Drupal/views/Tests/Plugin/StyleGridTest.php | 107 +++++++++++++ .../views/templates/views-view-grid.html.twig | 51 +++--- .../test_views/views.view.test_grid.yml | 71 +++++++++ core/modules/views/views.theme.inc | 175 +++++++++++---------- 7 files changed, 363 insertions(+), 132 deletions(-) create mode 100644 core/modules/views/lib/Drupal/views/Tests/Plugin/StyleGridTest.php create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml diff --git a/core/modules/views/config/schema/views.style.schema.yml b/core/modules/views/config/schema/views.style.schema.yml index 49ab0f8..dc6edd0 100644 --- a/core/modules/views/config/schema/views.style.schema.yml +++ b/core/modules/views/config/schema/views.style.schema.yml @@ -29,15 +29,24 @@ views.style.grid: columns: type: integer label: 'Number of columns' + automatic_width: + type: boolean + label: 'Automatic width' alignment: type: string label: 'Alignment' - fill_single_line: + row_class_custom: + type: string + label: 'Custom row classes' + row_class_default: type: boolean - label: 'Fill up single line' - summary: + label: 'Default views row classes' + col_class_custom: type: string - label: 'Table summary' + label: 'Custom column classes' + col_class_default: + type: boolean + label: 'Default views column classes' views.style.table: type: views_style diff --git a/core/modules/views/css/views.module.css b/core/modules/views/css/views.module.css index 7ca2f49..851a244 100644 --- a/core/modules/views/css/views.module.css +++ b/core/modules/views/css/views.module.css @@ -22,3 +22,13 @@ .view .progress-disabled { float: none; } + +/* grid style column align */ +.views-view-grid .views-col { + float: left; +} +.views-view-grid .views-row { + clear: both; + float: left; + width: 100%; +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/style/Grid.php b/core/modules/views/lib/Drupal/views/Plugin/views/style/Grid.php index 0631661..cb45df6 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/style/Grid.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/style/Grid.php @@ -33,28 +33,22 @@ class Grid extends StylePluginBase { protected $usesRowPlugin = TRUE; /** - * Does the style plugin support custom css class for the rows. - * - * @var bool - */ - protected $usesRowClass = TRUE; - - /** - * Set default options + * {@inheritdoc} */ protected function defineOptions() { $options = parent::defineOptions(); - $options['columns'] = array('default' => '4'); + $options['automatic_width'] = array('default' => TRUE); $options['alignment'] = array('default' => 'horizontal'); - $options['fill_single_line'] = array('default' => TRUE, 'bool' => TRUE); - $options['summary'] = array('default' => ''); - + $options['col_class_custom'] = array('default' => ''); + $options['col_class_default'] = array('default' => TRUE); + $options['row_class_custom'] = array('default' => ''); + $options['row_class_default'] = array('default' => TRUE); return $options; } /** - * Render the given style. + * {@inheritdoc} */ public function buildOptionsForm(&$form, &$form_state) { parent::buildOptionsForm($form, $form_state); @@ -63,7 +57,13 @@ public function buildOptionsForm(&$form, &$form_state) { '#title' => t('Number of columns'), '#default_value' => $this->options['columns'], '#required' => TRUE, - '#min' => 0, + '#min' => 1, + ); + $form['automatic_width'] = array( + '#type' => 'checkbox', + '#title' => t('Automatic width'), + '#description' => t('The width of each column will be calculated automatically based on the number of columns provided. If additional classes are entered or a theme injects classes based on a grid system, disabling this option may prove beneficial.'), + '#default_value' => $this->options['automatic_width'], ); $form['alignment'] = array( '#type' => 'radios', @@ -72,20 +72,36 @@ public function buildOptionsForm(&$form, &$form_state) { '#default_value' => $this->options['alignment'], '#description' => 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.'), ); - - $form['fill_single_line'] = array( + $form['col_class_default'] = array( + '#title' => t('Default column classes'), + '#description' => t('Add the default views column classes like views-col, col-1 and clearfix to the output. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.'), '#type' => 'checkbox', - '#title' => t('Fill up single line'), - '#description' => t('If you disable this option, a grid with only one row will have the same number of table cells () as items. Disabling it can cause problems with your CSS.'), - '#default_value' => !empty($this->options['fill_single_line']), + '#default_value' => $this->options['col_class_default'], ); - - $form['summary'] = array( + $form['col_class_custom'] = array( + '#title' => t('Custom column class'), + '#description' => t('Additional classes to provide on each column. Separated by a space.'), '#type' => 'textfield', - '#title' => t('Table summary'), - '#description' => t('This value will be displayed as table-summary attribute in the html. Set this for better accessiblity of your site.'), - '#default_value' => $this->options['summary'], + '#default_value' => $this->options['col_class'], ); + if ($this->usesFields()) { + $form['col_class_custom']['#description'] .= ' ' . t('You may use field tokens from as per the "Replacement patterns" used in "Rewrite the output of this field" for all fields.'); + } + $form['row_class_default'] = array( + '#title' => t('Default row classes'), + '#description' => t('Adds the default views row classes like views-row, row-1 and clearfix to the output. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.'), + '#type' => 'checkbox', + '#default_value' => $this->options['row_class_default'], + ); + $form['row_class_custom'] = array( + '#title' => t('Custom row class'), + '#description' => t('Additional classes to provide on each row. Separated by a space.'), + '#type' => 'textfield', + '#default_value' => $this->options['row_class_custom'], + ); + if ($this->usesFields()) { + $form['row_class_custom']['#description'] .= ' ' . t('You may use field tokens from as per the "Replacement patterns" used in "Rewrite the output of this field" for all fields.'); + } } } diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleGridTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleGridTest.php new file mode 100644 index 0000000..da2b95c --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleGridTest.php @@ -0,0 +1,107 @@ + 'Style: Grid', + 'description' => 'Tests the grid style plugin.', + 'group' => 'Views Plugins', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->enableViewsTestModule(); + } + + /** + * Tests the grid style. + */ + public function testGrid() { + $view = views_get_view('test_grid'); + foreach (array('horizontal', 'vertical') as $alignment) { + $this->assertGrid($view, $alignment, 5); + $this->assertGrid($view, $alignment, 4); + $this->assertGrid($view, $alignment, 3); + $this->assertGrid($view, $alignment, 2); + $this->assertGrid($view, $alignment, 1); + } + } + + /** + * Generates a grid and asserts that it is displaying correctly. + * + * @param \Drupal\views\ViewExecutable $view + * The executable to prepare. + * @param string $alignment + * The alignment of the grid to test. + * @param int $columns + * The number of columns in the grid to test. + */ + protected function assertGrid(ViewExecutable $view, $alignment, $columns) { + $view->setDisplay('default'); + $view->initStyle(); + $view->initHandlers(); + $view->initQuery(); + $view->style_plugin->options['alignment'] = $alignment; + $view->style_plugin->options['columns'] = $columns; + $this->executeView($view); + $output = $view->preview(); + $output = drupal_render($output); + $this->drupalSetContent($output, 'internal://test-grid'); + if (!in_array($alignment, $this->alignmentsTested)) { + $result = $this->xpath('//div[contains(@class, "views-view-grid") and contains(@class, :alignment) and contains(@class, :columns)]', array(':alignment' => $alignment, ':columns' => 'cols-' . $columns)); + $this->assertTrue(count($result), ucfirst($alignment) . " grid markup detected."); + $this->alignmentsTested[] = $alignment; + } + $width = '0'; + switch ($columns) { + case 5: $width = '20'; break; + case 4: $width = '25'; break; + case 3: $width = '33.3333'; break; + case 2: $width = '50'; break; + case 1: $width = '100'; break; + } + // Ensure last column exists. + $result = $this->xpath('//div[contains(@class, "views-col") and contains(@class, :columns) and starts-with(@style, :width)]', array(':columns' => 'col-' . $columns, ':width' => 'width: ' . $width)); + $this->assertTrue(count($result), ucfirst($alignment) . " $columns column grid: last column exists and automatic width calculated correctly."); + // Ensure no extra columns were generated. + $result = $this->xpath('//div[contains(@class, "views-col") and contains(@class, :columns)]', array(':columns' => 'col-' . ($columns + 1))); + $this->assertFalse(count($result), ucfirst($alignment) . " $columns column grid: no extraneous columns exist."); + } + +} diff --git a/core/modules/views/templates/views-view-grid.html.twig b/core/modules/views/templates/views-view-grid.html.twig index 8b63792..a5a813c 100644 --- a/core/modules/views/templates/views-view-grid.html.twig +++ b/core/modules/views/templates/views-view-grid.html.twig @@ -4,13 +4,18 @@ * Default theme implementation for views to display rows in a grid. * * Available variables: - * - attributes: HTML attributes for the table element. + * - attributes: HTML attributes for the wrapping element. * - title: The title of this group of rows. - * - rows: A list of rows. Each row contains a list of columns. - * - row_classes: HTML classes for each row including the row number and first - * or last. - * - column_classes: HTML classes for each column including the row number and - * first or last. + * - view: The view object. + * - rows: The rendered view results. + * - options: The view plugin style options. + * - items: A list of grid items. Each item contains a list of rows or columns. + * The order in what comes first (row or column) depends on which alignment + * type is chosen (horizontal or vertical). + * - 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() * @@ -20,16 +25,26 @@ {% if title %}

{{ title }}

{% endif %} - - - {% for row_number, columns in rows %} - - {% for column_number, item in columns %} - - {{ item }} - - {% endfor %} - + +{% if options.alignment == 'horizontal' %} + {% for row in items %} + + {% for column in row.content %} + + {{ column.content }} + {% endfor %} - - + + {% endfor %} +{% else %} + {% for column in items %} + + {% for row in column.content %} + + {{ row.content }} + + {% endfor %} + + {% endfor %} +{% endif %} + diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml new file mode 100644 index 0000000..c2a8915 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml @@ -0,0 +1,71 @@ +base_table: views_test_data +core: '8' +description: '' +status: '1' +display: + default: + display_options: + defaults: + fields: '0' + pager: '0' + pager_options: '0' + sorts: '0' + 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 + pager_options: { } + sorts: + id: + field: id + id: id + order: ASC + relationship: none + table: views_test_data + plugin_id: numeric + style: + type: grid + options: + grouping: { } + columns: '4' + automatic_width: '1' + alignment: horizontal + col_class_default: '1' + col_class_custom: '' + row_class_default: '1' + row_class_custom: '' + row: + type: fields + display_plugin: default + display_title: Master + id: default + position: '0' + page_1: + display_options: + path: test-grid + display_plugin: page + display_title: 'Page display' + id: page_1 + position: '1' +label: '' +id: test_grid +tag: '' diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index 3d1a84d..5ee5143 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -746,56 +746,102 @@ function template_preprocess_views_view_table(&$variables) { * - rows: An array of row items. Each row is an array of content. */ function template_preprocess_views_view_grid(&$variables) { - $view = $variables['view']; - $options = $view->style_plugin->options; - $handler = $view->style_plugin; - $default_row_class = isset($options['default_row_class']) ? $options['default_row_class'] : TRUE; - $row_class_special = isset($options['row_class_special']) ? $options['row_class_special'] : TRUE; + $options = $variables['options'] = $variables['view']->style_plugin->options; + $horizontal = ($options['alignment'] === 'horizontal'); - $columns = $options['columns']; - $variables['attributes']['class'][] = 'views-view-grid cols-' . $columns; + $variables['attributes']['class'] = array( + 'views-view-grid', + $options['alignment'], + 'cols-' . $options['columns'], + 'clearfix', + ); - $rows = array(); - $row_indexes = array(); + $col = 0; + $row = 0; + $items = array(); + $remainders = count($variables['rows']) % $options['columns']; + $num_rows = floor(count($variables['rows']) / $options['columns']); - if ($options['alignment'] == 'horizontal') { - $row = array(); - $col_count = 0; - $row_count = 0; - $count = 0; - foreach ($variables['rows'] as $row_index => $item) { - $count++; - $row[] = $item; - $row_indexes[$row_count][$col_count] = $row_index; - $col_count++; - if ($count % $columns == 0) { - $rows[] = $row; - $row = array(); - $col_count = 0; - $row_count++; + // Iterate over each rendered views result row. + foreach ($variables['rows'] as $item) { + + // Add the item. + if ($horizontal) { + $items[$row]['content'][$col]['content'] = $item; + } + else { + $items[$col]['content'][$row]['content'] = $item; + } + + // Create attributes for rows. + if (!$horizontal || ($horizontal && empty($items[$row]['attributes']))) { + $row_attributes = array('class' => array()); + // Add default views row classes. + if ($options['row_class_default']) { + $row_attributes['class'][] = 'views-row'; + $row_attributes['class'][] = 'row-' . ($row + 1); + if ($horizontal) { + $row_attributes['class'][] = 'clearfix'; + } + } + // Add custom row classes. + $row_class = array_filter(explode(' ', $options['row_class_custom'])); + if (!empty($row_class)) { + $row_attributes['class'] = array_merge($row_attributes['class'], $row_class); + } + // Add row attributes to the item. + if ($horizontal) { + $items[$row]['attributes'] = new Attribute($row_attributes); + } + else { + $items[$col]['content'][$row]['attributes'] = new Attribute($row_attributes); } } - if ($row) { - // Fill up the last line only if it's configured, but this is default. - if (!empty($handler->options['fill_single_line']) && count($rows)) { - for ($i = 0; $i < ($columns - $col_count); $i++) { - $row[] = ''; + + // Create attributes for columns. + if ($horizontal || (!$horizontal && empty($items[$col]['attributes']))) { + $col_attributes = array('class' => array()); + // Add default views column classes. + if ($options['col_class_default']) { + $col_attributes['class'][] = 'views-col'; + $col_attributes['class'][] = 'col-' . ($col + 1); + if (!$horizontal) { + $col_attributes['class'][] = 'clearfix'; } } - $rows[] = $row; + // Add custom column classes. + $col_class = array_filter(explode(' ', $options['col_class_custom'])); + if (!empty($col_class)) { + $col_attributes['class'] = array_merge($col_attributes['class'], $col_class); + } + // Add automatic width for columns. + if ($options['automatic_width']) { + $col_attributes['style'] = 'width: ' . (100 / $options['columns']) . '%;'; + } + // Add column attributes to the item. + if ($horizontal) { + $items[$row]['content'][$col]['attributes'] = new Attribute($col_attributes); + } + else { + $items[$col]['attributes'] = new Attribute($col_attributes); + } + } + + // Increase, decrease or reset appropriate integers. + if ($horizontal) { + if ($col == 0 && $col != ($options['columns'] - 1)) { + $col++; + } + elseif ($col >= ($options['columns'] - 1)) { + $col = 0; + $row++; + } + else { + $col++; + } } - } - else { - $num_rows = floor(count($variables['rows']) / $columns); - // The remainders are the 'odd' columns that are slightly longer. - $remainders = count($variables['rows']) % $columns; - $row = 0; - $col = 0; - foreach ($variables['rows'] as $count => $item) { - $rows[$row][$col] = $item; - $row_indexes[$row][$col] = $count; + else { $row++; - if (!$remainders && $row == $num_rows) { $row = 0; $col++; @@ -806,53 +852,10 @@ function template_preprocess_views_view_grid(&$variables) { $remainders--; } } - for ($i = 0; $i < count($rows[0]); $i++) { - // This should be a string so this is ok. - if (!isset($rows[count($rows) - 1][$i])) { - $rows[count($rows) - 1][$i] = ''; - } - } } - // Apply the row classes. - foreach ($rows as $row_number => $row) { - $row_classes = array(); - if ($default_row_class) { - $row_classes['class'][] = 'row-' . ($row_number + 1); - } - if ($row_class_special) { - if ($row_number == 0) { - $row_classes['class'][] = 'row-first'; - } - if (count($rows) == ($row_number + 1)) { - $row_classes['class'][] = 'row-last'; - } - } - $row_classes = new Attribute($row_classes); - - foreach ($rows[$row_number] as $column_number => $item) { - $variables['column_classes'][$row_number][$column_number] = array(); - if ($default_row_class) { - $variables['column_classes'][$row_number][$column_number]['class'][] = 'col-' . ($column_number + 1); - } - if ($row_class_special) { - if ($column_number == 0) { - $variables['column_classes'][$row_number][$column_number]['class'][] = 'col-first'; - } - elseif (count($rows[$row_number]) == ($column_number + 1)) { - $variables['column_classes'][$row_number][$column_number]['class'][] = 'col-last'; - } - } - if (isset($row_indexes[$row_number][$column_number]) && $column_class = $view->style_plugin->getRowClass($row_indexes[$row_number][$column_number])) { - $variables['column_classes'][$row_number][$column_number]['class'][] = $column_class; - } - $variables['column_classes'][$row_number][$column_number] = new Attribute($variables['column_classes'][$row_number][$column_number]); - } - } - $variables['rows'] = $rows; - if (!empty($handler->options['summary'])) { - $variables['attributes']['summary'] = $handler->options['summary']; - } + // Add items to the variables array. + $variables['items'] = $items; } /** -- 1.8.2