From 714b3320c01e9666e64da1392f2e7cf5739dc67f Mon Sep 17 00:00:00 2001 From: Tim Plunkett Date: Wed, 5 Sep 2012 09:08:22 -0400 Subject: [PATCH] Issue #1765824 by tim.plunkett: Added a way to map views fields to a certain meaning. --- .../views/Plugin/views/style/Mapping.php | 145 ++++++++++++++++++ .../views/Tests/Plugin/StyleMappingTest.php | 85 ++++++++++ lib/Drupal/views/Tests/Plugin/StyleTest.php | 23 +-- .../views/Tests/Plugin/StyleTestBase.php | 43 ++++++ .../Tests/Plugin/StyleUnformattedTest.php | 30 +--- .../config/views.view.test_style_mapping.yml | 57 +++++++ .../Plugin/views/style/MappingTest.php | 70 +++++++++ tests/views_test_data/views_test_data.module | 56 +++++++ 8 files changed, 458 insertions(+), 51 deletions(-) create mode 100644 lib/Drupal/views/Plugin/views/style/Mapping.php create mode 100644 lib/Drupal/views/Tests/Plugin/StyleMappingTest.php create mode 100644 lib/Drupal/views/Tests/Plugin/StyleTestBase.php create mode 100644 tests/views_test_config/config/views.view.test_style_mapping.yml create mode 100644 tests/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php diff --git a/lib/Drupal/views/Plugin/views/style/Mapping.php b/lib/Drupal/views/Plugin/views/style/Mapping.php new file mode 100644 index 0000000000..00a5d8acd1 --- /dev/null +++ b/lib/Drupal/views/Plugin/views/style/Mapping.php @@ -0,0 +1,145 @@ +defineMapping() as $key => $value) { + $default = !empty($value['#multiple']) ? array() : ''; + $options['mapping']['contains'][$key] = array( + 'default' => isset($value['#default_value']) ? $value['#default_value'] : $default, + ); + if (!empty($value['#toggle'])) { + $options['mapping']['contains']["toggle_$key"] = array( + 'default' => FALSE, + 'bool' => TRUE, + ); + } + } + + return $options; + } + + /** + * Overrides Drupal\views\Plugin\views\style\StylePluginBase::buildOptionsForm(). + */ + public function buildOptionsForm(&$form, &$form_state) { + parent::buildOptionsForm($form, $form_state); + + // Get the mapping. + $mapping = $this->defineMapping(); + + // Restrict the list of defaults to the mapping, in case they have changed. + $options = array_intersect_key($this->options['mapping'], $mapping); + + // Get the labels of the fields added to this display. + $field_labels = $this->display->handler->getFieldLabels(); + + // Provide some default values. + $defaults = array( + '#type' => 'select', + '#required' => FALSE, + '#multiple' => FALSE, + ); + + // For each mapping, add a select element to the form. + foreach ($options as $key => $value) { + // If the field is optional, add a 'None' value to the top of the options. + $field_options = array(); + $required = !empty($mapping[$key]['#required']); + if (!$required && empty($mapping[$key]['#multiple'])) { + $field_options = array('' => t('- None -')); + } + $field_options += $field_labels; + + // Optionally filter the available fields. + if (isset($mapping[$key]['#filter'])) { + $this->view->initHandlers(); + $this::$mapping[$key]['#filter']($field_options); + unset($mapping[$key]['#filter']); + } + + // These values must always be set. + $overrides = array( + '#options' => $field_options, + '#default_value' => $options[$key], + ); + + // Optionally allow the select to be toggleable. + if (!empty($mapping[$key]['#toggle'])) { + $form['mapping']["toggle_$key"] = array( + '#type' => 'checkbox', + '#title' => t('Use a custom %field_name', array('%field_name' => strtolower($mapping[$key]['#title']))), + '#default_value' => $this->options['mapping']["toggle_$key"], + ); + $overrides['#states']['visible'][':input[name="style_options[mapping][' . "toggle_$key" . ']"]'] = array('checked' => TRUE); + } + + $form['mapping'][$key] = $overrides + $mapping[$key] + $defaults; + } + } + + /** + * Overrides Drupal\views\Plugin\views\style\StylePluginBase::render(). + * + * Provides the mapping definition as an available variable. + */ + function render() { + return theme($this->themeFunctions(), array( + 'view' => $this->view, + 'options' => $this->options, + 'rows' => $this->view->result, + 'mapping' => $this->defineMapping(), + )); + } + +} diff --git a/lib/Drupal/views/Tests/Plugin/StyleMappingTest.php b/lib/Drupal/views/Tests/Plugin/StyleMappingTest.php new file mode 100644 index 0000000000..e34102283a --- /dev/null +++ b/lib/Drupal/views/Tests/Plugin/StyleMappingTest.php @@ -0,0 +1,85 @@ + 'Style: Mapping', + 'description' => 'Test mapping style functionality.', + 'group' => 'Views Plugins', + ); + } + + /** + * Overrides Drupal\views\Tests\ViewTestBase::getBasicView(). + */ + protected function getBasicView() { + return $this->createViewFromConfig('test_style_mapping'); + } + + /** + * Verifies that the fields were mapped correctly. + */ + public function testMappedOutput() { + $view = $this->getView(); + $output = $this->mappedOutputHelper($view); + $this->assertTrue(strpos($output, 'job') === FALSE, 'The job field is added to the view but not in the mapping.'); + + $view = $this->getView(); + $view->display['default']->handler->options['style_options']['mapping']['name_field'] = 'job'; + $output = $this->mappedOutputHelper($view); + $this->assertTrue(strpos($output, 'job') !== FALSE, 'The job field is added to the view and is in the mapping.'); + } + + /** + * Tests the mapping of fields. + * + * @param Drupal\views\View $view + * The view to test. + * + * @return string + * The view rendered as HTML. + */ + protected function mappedOutputHelper($view) { + $rendered_output = $view->preview(); + $this->storeViewPreview($rendered_output); + $rows = $this->elements->body->div->div->div; + $data_set = $this->dataSet(); + + $count = 0; + foreach ($rows as $row) { + $attributes = $row->attributes(); + $class = (string) $attributes['class'][0]; + $this->assertTrue(strpos($class, 'views-row-mapping-test') !== FALSE, 'Make sure that each row has the correct CSS class.'); + + foreach ($row->div as $field) { + // Split up the field-level class, the first part is the mapping name + // and the second is the field ID. + $field_attributes = $field->attributes(); + $name = strtok((string) $field_attributes['class'][0], '-'); + $field_id = strtok('-'); + + // The expected result is the mapping name and the field value, + // separated by ':'. + $expected_result = $name . ':' . $data_set[$count][$field_id]; + $actual_result = (string) $field; + $this->assertIdentical($expected_result, $actual_result, format_string('The fields were mapped successfully: %name => %field_id', array('%name' => $name, '%field_id' => $field_id))); + } + + $count++; + } + + return $rendered_output; + } + +} diff --git a/lib/Drupal/views/Tests/Plugin/StyleTest.php b/lib/Drupal/views/Tests/Plugin/StyleTest.php index 6e16e942a3..8e1365072a 100644 --- a/lib/Drupal/views/Tests/Plugin/StyleTest.php +++ b/lib/Drupal/views/Tests/Plugin/StyleTest.php @@ -8,12 +8,11 @@ namespace Drupal\views\Tests\Plugin; use stdClass; -use DOMDocument; /** * Tests some general style plugin related functionality. */ -class StyleTest extends PluginTestBase { +class StyleTest extends StyleTestBase { public static function getInfo() { return array( @@ -23,12 +22,6 @@ public static function getInfo() { ); } - protected function setUp() { - parent::setUp(); - - $this->enableViewsTestModule(); - } - /** * Tests the grouping legacy features of styles. */ @@ -244,20 +237,6 @@ function _testGrouping($stripped = FALSE) { } } - - /** - * Stores a view output in the elements. - */ - function storeViewPreview($output) { - $htmlDom = new DOMDocument(); - @$htmlDom->loadHTML($output); - if ($htmlDom) { - // It's much easier to work with simplexml than DOM, luckily enough - // we can just simply import our DOM tree. - $this->elements = simplexml_import_dom($htmlDom); - } - } - /** * Tests custom css classes. */ diff --git a/lib/Drupal/views/Tests/Plugin/StyleTestBase.php b/lib/Drupal/views/Tests/Plugin/StyleTestBase.php new file mode 100644 index 0000000000..20fb2d65cd --- /dev/null +++ b/lib/Drupal/views/Tests/Plugin/StyleTestBase.php @@ -0,0 +1,43 @@ +enableViewsTestModule(); + } + + /** + * Stores a view output in the elements. + */ + function storeViewPreview($output) { + $htmlDom = new DOMDocument(); + @$htmlDom->loadHTML($output); + if ($htmlDom) { + // It's much easier to work with simplexml than DOM, luckily enough + // we can just simply import our DOM tree. + $this->elements = simplexml_import_dom($htmlDom); + } + } + +} diff --git a/lib/Drupal/views/Tests/Plugin/StyleUnformattedTest.php b/lib/Drupal/views/Tests/Plugin/StyleUnformattedTest.php index b937825d9f..bcc2e8cb0a 100644 --- a/lib/Drupal/views/Tests/Plugin/StyleUnformattedTest.php +++ b/lib/Drupal/views/Tests/Plugin/StyleUnformattedTest.php @@ -7,19 +7,10 @@ namespace Drupal\views\Tests\Plugin; -use DOMDocument; - /** * Tests the default/unformatted row style. */ -class StyleUnformattedTest extends PluginTestBase { - - /** - * Stores all created nodes. - * - * @var array - */ - protected $nodes; +class StyleUnformattedTest extends StyleTestBase { public static function getInfo() { return array( @@ -29,25 +20,6 @@ public static function getInfo() { ); } - protected function setUp() { - parent::setUp(); - - $this->enableViewsTestModule(); - } - - /** - * Stores a view output in the elements. - */ - function storeViewPreview($output) { - $htmlDom = new DOMDocument(); - @$htmlDom->loadHTML($output); - if ($htmlDom) { - // It's much easier to work with simplexml than DOM, luckily enough - // we can just simply import our DOM tree. - $this->elements = simplexml_import_dom($htmlDom); - } - } - /** * Take sure that the default css classes works as expected. */ diff --git a/tests/views_test_config/config/views.view.test_style_mapping.yml b/tests/views_test_config/config/views.view.test_style_mapping.yml new file mode 100644 index 0000000000..47223acfa1 --- /dev/null +++ b/tests/views_test_config/config/views.view.test_style_mapping.yml @@ -0,0 +1,57 @@ +api_version: '3.0' +base_table: views_test +core: '8' +description: '' +disabled: '0' +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 + job: + field: job + id: job + relationship: none + table: views_test + name: + field: name + id: name + relationship: none + table: views_test + pager: + options: + offset: '0' + type: none + pager_options: { } + sorts: + id: + field: id + id: id + order: ASC + relationship: none + table: views_test + style_options: + mapping: + name_field: name + numeric_field: + age: age + title_field: name + toggle_numeric_field: '1' + toggle_title_field: '1' + style_plugin: mapping_test + display_plugin: default + display_title: Master + id: default + position: '0' +human_name: '' +name: test_style_mapping +tag: '' diff --git a/tests/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php b/tests/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php new file mode 100644 index 0000000000..2336d04bb4 --- /dev/null +++ b/tests/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php @@ -0,0 +1,70 @@ + array( + '#title' => t('Title field'), + '#description' => t('Choose the field with the custom title.'), + '#toggle' => TRUE, + '#required' => TRUE, + ), + 'name_field' => array( + '#title' => t('Name field'), + '#description' => t('Choose the field with the custom name.'), + ), + 'numeric_field' => array( + '#title' => t('Numeric field'), + '#description' => t('Select one or more numeric fields.'), + '#multiple' => TRUE, + '#toggle' => TRUE, + '#filter' => 'filterNumericFields', + '#required' => TRUE, + ), + ); + } + + /** + * Restricts the allowed fields to only numeric fields. + * + * @param array $fields + * An array of field labels, keyed by the field ID. + */ + protected function filterNumericFields(&$fields) { + foreach ($this->view->field as $id => $field) { + if (!($field instanceof Numeric)) { + unset($fields[$id]); + } + } + } + +} diff --git a/tests/views_test_data/views_test_data.module b/tests/views_test_data/views_test_data.module index 3583229ec7..5e7b4f5735 100644 --- a/tests/views_test_data/views_test_data.module +++ b/tests/views_test_data/views_test_data.module @@ -68,3 +68,59 @@ function views_test_data_views_post_build(View &$view) { } } } + +/** + * Implements hook_preprocess_HOOK() for theme_views_view_mapping_test(). + */ +function template_preprocess_views_view_mapping_test(&$variables) { + $variables['element'] = array(); + + foreach ($variables['rows'] as $delta => $row) { + $fields = array(); + foreach ($variables['options']['mapping'] as $type => $field_names) { + if (!is_array($field_names)) { + $field_names = array($field_names); + } + foreach ($field_names as $field_name) { + if ($value = $variables['view']->style_plugin->get_field($delta, $field_name)) { + $fields[$type . '-' . $field_name] = $type . ':' . $value; + } + } + } + + // If there are no fields in this row, skip to the next one. + if (empty($fields)) { + continue; + } + + // Build a container for the row. + $variables['element'][$delta] = array( + '#type' => 'container', + '#attributes' => array( + 'class' => array( + 'views-row-mapping-test', + ), + ), + ); + + // Add each field to the row. + foreach ($fields as $key => $render) { + $variables['element'][$delta][$key] = array( + '#children' => $render, + '#type' => 'container', + '#attributes' => array( + 'class' => array( + $key, + ), + ), + ); + } + } +} + +/** + * Returns HTML for the Mapping Test style. + */ +function theme_views_view_mapping_test($variables) { + return drupal_render($variables['element']); +} -- GitLab