diff --git a/core/modules/views_ui/css/views_ui.admin.theme.css b/core/modules/views_ui/css/views_ui.admin.theme.css
index 4ab55c46575400f2c20db3d85dd0740654c743d0..460e75eaa2adcf9b6a6256189b64a4995fcb30c6 100644
--- a/core/modules/views_ui/css/views_ui.admin.theme.css
+++ b/core/modules/views_ui/css/views_ui.admin.theme.css
@@ -170,9 +170,9 @@ details.box-padding {
margin-bottom: 6px;
margin-top: 6px;
}
-.views-ui-view-title {
+.views-ui-view-name h3 {
font-weight: bold;
- margin-top: 0;
+ margin: 0.25em 0;
}
.view-changed {
margin-bottom: 21px;
@@ -183,22 +183,33 @@ details.box-padding {
margin-bottom: 0;
margin-top: 18px;
}
+.views-ui-view-displays ul {
+ margin-left: 0; /* LTR */
+ padding-left: 0; /* LTR */
+ list-style: none;
+}
+[dir="rtl"] .views-ui-view-displays ul {
+ margin-right: 0;
+ padding-right: 0;
+ margin-left: inherit;
+ padding-left: inherit;
+}
/* These header classes are ambiguous and should be scoped to th elements */
.views-ui-name {
- width: 18%;
+ width: 20%;
}
.views-ui-description {
- width: 26%;
+ width: 30%;
}
-.views-ui-tag {
- width: 8%;
+.views-ui-machine-name {
+ width: 15%;
}
-.views-ui-path {
- width: auto;
+.views-ui-displays {
+ width: 25%;
}
.views-ui-operations {
- width: 24%;
+ width: 10%;
}
/**
diff --git a/core/modules/views_ui/src/Tests/DefaultViewsTest.php b/core/modules/views_ui/src/Tests/DefaultViewsTest.php
index 3b3f2a734327254399ba00288446137976f98f41..08fcae8ea4eef534935c48eb8eb45b8ad5f8d2be 100644
--- a/core/modules/views_ui/src/Tests/DefaultViewsTest.php
+++ b/core/modules/views_ui/src/Tests/DefaultViewsTest.php
@@ -161,10 +161,11 @@ function testDefaultViews() {
*/
function testSplitListing() {
// Build a re-usable xpath query.
- $xpath = '//div[@id="views-entity-list"]/div[@class = :status]/table//tr[@title = :title]';
+ $xpath = '//div[@id="views-entity-list"]/div[@class = :status]/table//td/text()[contains(., :title)]';
+
$arguments = array(
':status' => 'views-list-section enabled',
- ':title' => t('Machine name: test_view_status'),
+ ':title' => 'test_view_status',
);
$this->drupalGet('admin/structure/views');
diff --git a/core/modules/views_ui/src/Tests/XssTest.php b/core/modules/views_ui/src/Tests/XssTest.php
index 8eac3a687368f282f31d9e6b86786c9378c9dbd5..1d61f1942725e7ed964441f26429bf33232bbc94 100644
--- a/core/modules/views_ui/src/Tests/XssTest.php
+++ b/core/modules/views_ui/src/Tests/XssTest.php
@@ -17,9 +17,6 @@ class XssTest extends UITestBase {
public static $modules = array('node', 'user', 'views_ui', 'views_ui_test');
public function testViewsUi() {
- $this->drupalGet('admin/structure/views');
- $this->assertEscaped(', ', 'The view tag is properly escaped.');
-
$this->drupalGet('admin/structure/views/view/sa_contrib_2013_035');
$this->assertEscaped('', 'Field admin label is properly escaped.');
diff --git a/core/modules/views_ui/src/ViewListBuilder.php b/core/modules/views_ui/src/ViewListBuilder.php
index 112e1c457921daa349d9a27d0b17a03e70038936..b38a15f75bdf9e0331fe6374c26dc1b001aa3f81 100644
--- a/core/modules/views_ui/src/ViewListBuilder.php
+++ b/core/modules/views_ui/src/ViewListBuilder.php
@@ -89,34 +89,30 @@ public function buildRow(EntityInterface $view) {
'data' => array(
'view_name' => array(
'data' => array(
- '#theme' => 'views_ui_view_info',
- '#view' => $view,
- '#displays' => $this->getDisplaysList($view)
+ '#plain_text' => $view->label(),
),
),
- 'description' => array(
+ 'machine_name' => array(
'data' => array(
- '#plain_text' => $view->get('description'),
+ '#plain_text' => $view->id(),
),
- 'data-drupal-selector' => 'views-table-filter-text-source',
),
- 'tag' => array(
+ 'description' => array(
'data' => array(
- '#plain_text' => $view->get('tag'),
+ '#plain_text' => $view->get('description'),
),
- 'data-drupal-selector' => 'views-table-filter-text-source',
),
- 'path' => array(
+ 'displays' => array(
'data' => array(
- '#theme' => 'item_list',
- '#items' => $this->getDisplayPaths($view),
- '#context' => ['list_style' => 'comma-list'],
+ '#theme' => 'views_ui_view_displays_list',
+ '#displays' => $this->getDisplaysList($view),
),
),
'operations' => $row['operations'],
),
- 'title' => $this->t('Machine name: @name', array('@name' => $view->id())),
- 'class' => array($view->status() ? 'views-ui-list-enabled' : 'views-ui-list-disabled'),
+ '#attributes' => array(
+ 'class' => array($view->status() ? 'views-ui-list-enabled' : 'views-ui-list-disabled'),
+ ),
);
}
@@ -127,23 +123,33 @@ public function buildHeader() {
return array(
'view_name' => array(
'data' => $this->t('View name'),
- 'class' => array('views-ui-name'),
+ '#attributes' => array(
+ 'class' => array('views-ui-name'),
+ ),
+ ),
+ 'machine_name' => array(
+ 'data' => $this->t('Machine name'),
+ '#attributes' => array(
+ 'class' => array('views-ui-machine-name'),
+ ),
),
'description' => array(
'data' => $this->t('Description'),
- 'class' => array('views-ui-description'),
- ),
- 'tag' => array(
- 'data' => $this->t('Tag'),
- 'class' => array('views-ui-tag'),
+ '#attributes' => array(
+ 'class' => array('views-ui-description'),
+ ),
),
- 'path' => array(
- 'data' => $this->t('Path'),
- 'class' => array('views-ui-path'),
+ 'displays' => array(
+ 'data' => $this->t('Displays'),
+ '#attributes' => array(
+ 'class' => array('views-ui-displays'),
+ ),
),
'operations' => array(
'data' => $this->t('Operations'),
- 'class' => array('views-ui-operations'),
+ '#attributes' => array(
+ 'class' => array('views-ui-operations'),
+ ),
),
);
}
@@ -196,13 +202,13 @@ public function render() {
'#type' => 'search',
'#title' => $this->t('Filter'),
'#title_display' => 'invisible',
- '#size' => 40,
- '#placeholder' => $this->t('Filter by view name or description'),
+ '#size' => 60,
+ '#placeholder' => $this->t('Filter by view name, machine name, description, or display path'),
'#attributes' => array(
'class' => array('views-filter-text'),
'data-table' => '.views-listing-table',
'autocomplete' => 'off',
- 'title' => $this->t('Enter a part of the view name or description to filter by.'),
+ 'title' => $this->t('Enter a part of the view name, machine name, description, or display path to filter by.'),
),
);
@@ -212,12 +218,9 @@ public function render() {
$list[$status]['#type'] = 'container';
$list[$status]['#attributes'] = array('class' => array('views-list-section', $status));
$list[$status]['table'] = array(
- '#type' => 'table',
- '#attributes' => array(
- 'class' => array('views-listing-table'),
- ),
- '#header' => $this->buildHeader(),
- '#rows' => array(),
+ '#theme' => 'views_ui_views_listing_table',
+ '#headers' => $this->buildHeader(),
+ '#attributes' => array('class' => array('views-listing-table', $status)),
);
foreach ($entities[$status] as $entity) {
$list[$status]['table']['#rows'][$entity->id()] = $this->buildRow($entity);
@@ -242,46 +245,33 @@ public function render() {
*/
protected function getDisplaysList(EntityInterface $view) {
$displays = array();
- foreach ($view->get('display') as $display) {
- $definition = $this->displayManager->getDefinition($display['display_plugin']);
- if (!empty($definition['admin'])) {
- // Cast the admin label to a string since it is an object.
- // @see \Drupal\Core\StringTranslation\TranslatableMarkup
- $displays[] = (string) $definition['admin'];
- }
- }
-
- sort($displays);
- return $displays;
- }
- /**
- * Gets a list of paths assigned to the view.
- *
- * @param \Drupal\Core\Entity\EntityInterface $view
- * The view entity.
- *
- * @return array
- * An array of paths for this view.
- */
- protected function getDisplayPaths(EntityInterface $view) {
- $all_paths = array();
$executable = $view->getExecutable();
$executable->initDisplay();
foreach ($executable->displayHandlers as $display) {
- if ($display->hasPath()) {
- $path = $display->getPath();
- if ($view->status() && strpos($path, '%') === FALSE) {
- // @todo Views should expect and store a leading /. See:
- // https://www.drupal.org/node/2423913
- $all_paths[] = \Drupal::l('/' . $path, Url::fromUserInput('/' . $path));
- }
- else {
- $all_paths[] = '/' . $path;
+ $rendered_path = FALSE;
+ $definition = $display->getPluginDefinition();
+ if (!empty($definition['admin'])) {
+ if ($display->hasPath()) {
+ $path = $display->getPath();
+ if ($view->status() && strpos($path, '%') === FALSE) {
+ // @todo Views should expect and store a leading /. See:
+ // https://www.drupal.org/node/2423913
+ $rendered_path = \Drupal::l('/' . $path, Url::fromUserInput('/' . $path));
+ }
+ else {
+ $rendered_path = '/' . $path;
+ }
}
+ $displays[] = array(
+ 'display' => $definition['admin'],
+ 'path' => $rendered_path,
+ );
}
}
- return array_unique($all_paths);
+
+ sort($displays);
+ return $displays;
}
}
diff --git a/core/modules/views_ui/templates/views-ui-view-displays-list.html.twig b/core/modules/views_ui/templates/views-ui-view-displays-list.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..1c7d6210078c6868d4c0341a3072469c3d6dbc11
--- /dev/null
+++ b/core/modules/views_ui/templates/views-ui-view-displays-list.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+ * @file
+ * Default theme implementation for views displays on the views listing page.
+ *
+ * Available variables:
+ * - displays: Contains multiple display instances. Each display contains:
+ * - display: Display name.
+ * - path: Path to display, if any.
+ *
+ * @ingroup themeable
+ */
+#}
+
+ {% for display in displays %}
+ -
+ {% if display.path %}
+ {{ display.display }} ({{ display.path }})
+ {% else %}
+ {{ display.display }}
+ {% endif %}
+
+ {% endfor %}
+
diff --git a/core/modules/views_ui/templates/views-ui-views-listing-table.html.twig b/core/modules/views_ui/templates/views-ui-views-listing-table.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..207462e88bccc1b77b84d562aec1c872236d272e
--- /dev/null
+++ b/core/modules/views_ui/templates/views-ui-views-listing-table.html.twig
@@ -0,0 +1,49 @@
+{#
+/**
+ * @file
+ * Default theme implementation for views listing table.
+ *
+ * Available variables:
+ * - headers: Contains table headers.
+ * - rows: Contains multiple rows. Each row contains:
+ * - view_name: The human-readable name of the view.
+ * - machine_name: Machine name of the view.
+ * - description: The description of the view.
+ * - displays: List of displays attached to the view.
+ * - operations: List of available operations.
+ *
+ * @see template_preprocess_views_ui_views_listing_table()
+ *
+ * @ingroup themeable
+ */
+#}
+
+
+
+ {% for header in headers %}
+ {{ header.data }} |
+ {% endfor %}
+
+
+
+ {% for row in rows %}
+
+
+ {{ row.data.view_name.data }}
+ |
+
+ {{ row.data.machine_name.data }}
+ |
+
+ {{ row.data.description.data }}
+ |
+
+ {{ row.data.displays.data }}
+ |
+
+ {{ row.data.operations.data }}
+ |
+
+ {% endfor %}
+
+
diff --git a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
index 215728cd5f43af7e0047b780beea7795ab79314a..9b646964eb04dec4228eb13fb782b5c99628345b 100644
--- a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
+++ b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
@@ -152,21 +152,34 @@ public function testBuildRowEntityList() {
$view_list_builder = new TestViewListBuilder($entity_type, $storage, $display_manager);
$view_list_builder->setStringTranslation($this->getStringTranslationStub());
+ // Create new view with test values.
$view = new View($values, 'view');
+ // Get the row object created by ViewListBuilder for this test view.
$row = $view_list_builder->buildRow($view);
+ // Expected output array for view's displays.
$expected_displays = array(
- 'Embed admin label',
- 'Page admin label',
- 'Page admin label',
- 'Page admin label',
+ '0' => array(
+ 'display' => 'Embed admin label',
+ 'path' => FALSE,
+ ),
+ '1' => array(
+ 'display' => 'Page admin label',
+ 'path' => '/',
+ ),
+ '2' => array(
+ 'display' => 'Page admin label',
+ 'path' => '/',
+ ),
+ '3' => array(
+ 'display' => 'Page admin label',
+ 'path' => '/test_page',
+ ),
);
- $this->assertEquals($expected_displays, $row['data']['view_name']['data']['#displays']);
- $display_paths = $row['data']['path']['data']['#items'];
- // These values will be escaped by Twig when rendered.
- $this->assertEquals('/test_page, /, /', implode(', ', $display_paths));
+ // Compare the expected and generated output.
+ $this->assertEquals($expected_displays, $row['data']['displays']['data']['#displays']);
}
}
diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module
index 497636809baa71b341b1a9977e334b0e174e9ac4..165e8669efb4cc9c53a91e90038a4b536287b83e 100644
--- a/core/modules/views_ui/views_ui.module
+++ b/core/modules/views_ui/views_ui.module
@@ -80,12 +80,25 @@ function views_ui_theme() {
'file' => 'views_ui.theme.inc',
),
- // list views
+ // Legacy theme hook for displaying views info.
'views_ui_view_info' => array(
'variables' => array('view' => NULL, 'displays' => NULL),
'file' => 'views_ui.theme.inc',
),
+ // List views.
+ 'views_ui_views_listing_table' => array(
+ 'variables' => array(
+ 'headers' => NULL,
+ 'rows' => NULL,
+ 'attributes' => array(),
+ ),
+ 'file' => 'views_ui.theme.inc',
+ ),
+ 'views_ui_view_displays_list' => array(
+ 'variables' => array('displays' => array()),
+ ),
+
// Group of filters.
'views_ui_build_group_filter_form' => array(
'render element' => 'form',
diff --git a/core/modules/views_ui/views_ui.theme.inc b/core/modules/views_ui/views_ui.theme.inc
index 3b5867894d39721e4e8386d868aee6e7ad9bc01c..926f82ab309d439ff5e953d122c74a9a028dbf41 100644
--- a/core/modules/views_ui/views_ui.theme.inc
+++ b/core/modules/views_ui/views_ui.theme.inc
@@ -11,6 +11,7 @@
use Drupal\Core\Render\Element\Checkboxes;
use Drupal\Core\Render\Element\Radios;
use Drupal\Core\Url;
+use Drupal\Core\Template\Attribute;
/**
* Prepares variables for Views UI display tab setting templates.
@@ -42,6 +43,31 @@ function template_preprocess_views_ui_display_tab_setting(&$variables) {
}
}
+/**
+ * Prepares variables for Views UI view listing templates.
+ *
+ * Default template: views-ui-view-listing-table.html.twig.
+ *
+ * @param array $variables
+ * An associative array containing:
+ * - headers: An associative array containing the headers for the view
+ * listing table.
+ * - rows: An associative array containing the rows data for the view
+ * listing table.
+ */
+function template_preprocess_views_ui_views_listing_table(&$variables) {
+ // Convert the attributes to valid attribute objects.
+ foreach ($variables['headers'] as $key => $header) {
+ $variables['headers'][$key]['attributes'] = new Attribute($header['#attributes']);
+ }
+
+ if (!empty($variables['rows'])) {
+ foreach ($variables['rows'] as $key => $row) {
+ $variables['rows'][$key]['attributes'] = new Attribute($row['#attributes']);
+ }
+ }
+}
+
/**
* Prepares variables for Views UI display tab bucket templates.
*
diff --git a/core/themes/stable/css/views_ui/views_ui.admin.theme.css b/core/themes/stable/css/views_ui/views_ui.admin.theme.css
index 3bf3290c88967a9a27a8c2a65e49e86cad36421d..3a2afb56459d98a8302f5d8eafa790bc51dd34c4 100644
--- a/core/themes/stable/css/views_ui/views_ui.admin.theme.css
+++ b/core/themes/stable/css/views_ui/views_ui.admin.theme.css
@@ -170,9 +170,9 @@ details.box-padding {
margin-bottom: 6px;
margin-top: 6px;
}
-.views-ui-view-title {
+.views-ui-view-name h3 {
font-weight: bold;
- margin-top: 0;
+ margin: 0.25em 0;
}
.view-changed {
margin-bottom: 21px;
@@ -183,22 +183,33 @@ details.box-padding {
margin-bottom: 0;
margin-top: 18px;
}
+.views-ui-view-displays ul {
+ margin-left: 0; /* LTR */
+ padding-left: 0; /* LTR */
+ list-style: none;
+}
+[dir="rtl"] .views-ui-view-displays ul {
+ margin-right: 0;
+ padding-right: 0;
+ margin-left: inherit;
+ padding-left: inherit;
+}
/* These header classes are ambiguous and should be scoped to th elements */
.views-ui-name {
- width: 18%;
+ width: 20%;
}
.views-ui-description {
- width: 26%;
+ width: 30%;
}
-.views-ui-tag {
- width: 8%;
+.views-ui-machine-name {
+ width: 15%;
}
-.views-ui-path {
- width: auto;
+.views-ui-displays {
+ width: 25%;
}
.views-ui-operations {
- width: 24%;
+ width: 10%;
}
/**
diff --git a/core/themes/stable/templates/admin/views-ui-view-displays-list.html.twig b/core/themes/stable/templates/admin/views-ui-view-displays-list.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..e6e5b021270a47ca3afea904722e1e197b617805
--- /dev/null
+++ b/core/themes/stable/templates/admin/views-ui-view-displays-list.html.twig
@@ -0,0 +1,22 @@
+{#
+/**
+ * @file
+ * Theme override for views displays on the views listing page.
+ *
+ * Available variables:
+ * - displays: Contains multiple display instances. Each display contains:
+ * - display: Display name.
+ * - path: Path to display, if any.
+ */
+#}
+
+ {% for display in displays %}
+ -
+ {% if display.path %}
+ {{ display.display }} ({{ display.path }})
+ {% else %}
+ {{ display.display }}
+ {% endif %}
+
+ {% endfor %}
+
diff --git a/core/themes/stable/templates/admin/views-ui-views-listing-table.html.twig b/core/themes/stable/templates/admin/views-ui-views-listing-table.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..bc12a0a6efba881a374f55e368c63e0e3aff9d94
--- /dev/null
+++ b/core/themes/stable/templates/admin/views-ui-views-listing-table.html.twig
@@ -0,0 +1,47 @@
+{#
+/**
+ * @file
+ * Theme override for views listing table.
+ *
+ * Available variables:
+ * - headers: Contains table headers.
+ * - rows: Contains multiple rows. Each row contains:
+ * - view_name: The human-readable name of the view.
+ * - machine_name: Machine name of the view.
+ * - description: The description of the view.
+ * - displays: List of displays attached to the view.
+ * - operations: List of available operations.
+ *
+ * @see template_preprocess_views_ui_views_listing_table()
+ */
+#}
+
+
+
+ {% for header in headers %}
+ {{ header.data }} |
+ {% endfor %}
+
+
+
+ {% for row in rows %}
+
+
+ {{ row.data.view_name.data }}
+ |
+
+ {{ row.data.machine_name.data }}
+ |
+
+ {{ row.data.description.data }}
+ |
+
+ {{ row.data.displays.data }}
+ |
+
+ {{ row.data.operations.data }}
+ |
+
+ {% endfor %}
+
+