Suggested by catch: #1805996-15: [META] Views in Drupal Core
Is there an issue for this stuff? Views should be able to make the assumption that people use #attached properly or have to fix their code.

+  /**
+   * Start caching javascript, css and other out of band info.
+   *
+   * This takes a snapshot of the current system state so that we don't
+   * duplicate it. Later on, when gather_headers() is run, this information
+   * will be removed so that we don't hold onto it.
+   */
+  function cache_start() {
+    $this->storage['head'] = drupal_add_html_head();
+    $this->storage['css'] = drupal_add_css();
+    $this->storage['js'] = drupal_add_js();
+  }
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner’s picture

It's currently certainly problematic because views is not using an render array to render itself, so where should, for example a style plugin, attach it files to.

tim.plunkett’s picture

As a short term, instead of doing

drupal_add_css($whatever);
return $output;

We could do

return array(
  '#markup' => $output',
  '#attached' => array(
    'css' => array(
      $whatever,
    ),
  ),
);
dawehner’s picture

I'm not really sure how we get $whatever. If everything is using render arrays, this should work automatically right?

If we don't use render arrays we could store the css/js on the view object and then set it in the returning render array.

moshe weitzman’s picture

Would be fantastic to fix this for D8. I'm happy to help if folks have questions. In general, I think you have to pass a $build element around and let modules/plugins enrich it with css/js/markup, etc. I guess build could be right on the $view object

dawehner’s picture

Assigned: Unassigned » dawehner

Assign to myself.d

dawehner’s picture

dawehner’s picture

Status: Active » Needs review
FileSize
10.64 KB

Even though if we store the build array/the attached css/js we can't really figure out caching
perfectly.

Here comes an example: A view with node teasers, which adds a special css for the teaser. To figure out caching,
at least from my perspective you have to know each css/js files and therefore the only approach is recording the css/js.

One thing we can do is to attach this css/js in the outer render element.

So here is a patch.

    $css = drupal_array_merge_deep(drupal_add_css(), isset($this->view->attached['css']) ? $this->view->attached['css'] : array());
    $js = drupal_array_merge_deep(drupal_add_js(), isset($this->view->attached['js']) ? $this->view->attached['js'] : array());

I'm totally not sure about this piece of code.

Status: Needs review » Needs work

The last submitted patch, views-1811828-7.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
10.91 KB

Get rid of the problem in the patch.

tim.plunkett’s picture

+++ b/lib/Drupal/views/Plugin/views/cache/CachePluginBase.phpundefined
@@ -247,12 +247,14 @@ abstract class CachePluginBase extends PluginBase {
+    $css = drupal_array_merge_deep(drupal_add_css(), isset($this->view->attached['css']) ? $this->view->attached['css'] : array());
...
+    $js = drupal_array_merge_deep(drupal_add_js(), isset($this->view->attached['js']) ? $this->view->attached['js'] : array());

These lines are really confusing already, can we instantiate $this->view->attached['whatever'] as an empty array not in this line?

+++ b/lib/Drupal/views/Tests/Plugin/CacheTest.phpundefined
@@ -83,7 +83,7 @@ class CacheTest extends PluginTestBase {
-  function testTimeCaching() {
+  function _testTimeCaching() {

@@ -126,7 +126,7 @@ class CacheTest extends PluginTestBase {
-  function testNoneCaching() {
+  function _testNoneCaching() {

?

+++ b/lib/Drupal/views/ViewExecutable.phpundefined
@@ -397,6 +397,17 @@ class ViewExecutable {
+   * An array of js/css/libraries, which belongs to the view.

JS/CSS

+++ b/lib/Views/block/Plugin/views/display/Block.phpundefined
@@ -77,7 +77,11 @@ class Block extends DisplayPluginBase {
+    $render = $this->view->render();
+    $info['content'] = array(
+      '#markup' => $render,

Is the $render variable used later in the function? If not, might as well avoid the temporary variable

moshe weitzman’s picture

+++ b/lib/Views/block/Plugin/views/display/Block.phpundefined
@@ -77,7 +77,11 @@ class Block extends DisplayPluginBase {
+    $render = $this->view->render();
+    $info['content'] = array(
+      '#markup' => $render,

Don't know how plausible it is, but the end goal should be to not render to HTML ourselves, but rather to pass a render array back to the caller that has a #theme on it. Let Drupal do the themeing later. We have #pre_render if we need to do some processing before we themeing occurs.

tim.plunkett’s picture

Assigned: dawehner » tim.plunkett
Status: Needs review » Needs work

Okay, I'm working on #11

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
12.84 KB

This required a lot of changes that would otherwise seem out of scope, but they're all positive ones:

  • Use hook_library_info
  • Force usage of render arrays where possible
  • Remove views_add_js()

Status: Needs review » Needs work

The last submitted patch, views-1811828-13.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
14.71 KB
2.13 KB

So we JUST introduced the title area, and already it's breaking things.

moshe weitzman’s picture

Status: Needs review » Needs work

Awesome!

+ $vars['rows'] = drupal_render($vars['rows']);. Calls to drupal_render() are a code smell. I don't really care when they happen in Views UI such as in preview() method, but ones that happen inside of regular page processing are an issue. Specifically, the one in template_process_views_view() should be moved to the a render($content) or render($rows) in template file(s).

dawehner’s picture

+++ b/lib/Drupal/views/Plugin/views/display/Page.phpundefined
@@ -253,6 +253,10 @@ public function execute() {
+    // Because the empty area can alter the title, render it now.
+    $empty = empty($this->view->result);
+    $this->renderArea('empty', $empty);

If we have problems like this we should better set the title on pre_render? It feels smelly to require such hacks :(

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
6.45 KB
20.74 KB

Yeah, that hack should definitely be revisited before commit, I'm just trying to get tests to pass for now.

This should fix the bugs, and address #16.

dawehner’s picture

Status: Needs review » Needs work

Here are some points why building up an render array might not be the best idea.
They sure are sort of influenced of the problems we had with converting views in drupal 7 to render arrays, and failed especially in terms of backwards comparability, but sure this IS the time when we can actually do it right, whatever this ominous word means.
So please let discuss that.

  • Memory usage: This is one point which has been criticized by many people on the topic of render arrays.
    If you do really late rendering of a view, in the global page array, you can't throw away the view and all the other objects containing
    to a executed view. This might be easy for a simple view page, but if you have a huge and complicated frontpage with a slideshow here, and a teaser-list there, at least from my point this could be problematic. Please correct me if i'm wrong
  • Building up a big render array also doesn't seem to fit into the layouts plan, see #1812720: Implement the new panels-ish controller [it's a good purple]


+++ b/lib/Drupal/views/ViewExecutable.phpundefined
@@ -406,6 +417,9 @@ public function __construct(ViewStorage $storage) {
+    // Add the default css for a view.
+    $this->attached['css'][] = drupal_get_path('module', 'views') . '/css/views.base.css';

We might be better put this into hook_library, i'm not sure what common practise is for single css files.

+++ b/views.moduleundefined
@@ -1232,25 +1230,24 @@ function views_add_css($file) {
+function views_library_info() {
+  $path = drupal_get_path('module', 'views') . '/js';
+  $libraries['views.ajax'] = array(
+    'title' => 'Views AJAX',
+    'version' => VERSION,
+    'js' => array(
+      "$path/base.js" => array('group' => JS_DEFAULT),
+      "$path/ajax_view.js" => array('group' => JS_DEFAULT),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery.form'),
+      array('system', 'drupal.ajax'),
+    ),
+  );
+
+  return $libraries;

I really like that!

tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned
Status: Needs work » Needs review
FileSize
22.24 KB

The rest of our CSS and JS should be turned into libraries as well, but that should be another issue.

I think that since render arrays are still "the right way" in D8, we should wait to see what happens. If anything, when the "new way" gets adopted, we'll be able to convert from render arrays to that, like the rest of core.

This uncomments those tests and cleans up a bit of extra code.

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett

Oh, I forgot I still have to take out that one hack, reassigning.

dawehner’s picture

In order to have a proper render array for a view, and based on that nice css/js
caching we should have a proper render array, which have stored at least the following points

  • #cache
  • #attached
  • #cache_tags

As we would have memory problems storing all the views objects during the full request,
we came up with using the view element, which already exists in drupal 8.
It looks like:

array(
    '#name' => 'name',
    '#arguments' => array(),
    '#display_id' => array(),
)

The pre_render array will set a $view->build (or similar) which can be altered by any object
during the lifetime of a view. Style plugins for example would add additional CSS/JS files,
and cache plugins #cache.

On top of that we could convert the internal rendering of the view, but this is simply not needed for this issue.

tim.plunkett’s picture

Assigned: tim.plunkett » dawehner
FileSize
23.43 KB

I think this could go in as is, and #22 could be a follow-up. This already passes tests. But I leave that call to @dawehner.

tim.plunkett’s picture

FileSize
2.8 KB

Forgot interdiff

tim.plunkett’s picture

For the next reroll, change

-        foreach ($this->display_handler->getHandlers($area) as $handler) {
+        foreach ($this->$area as $handler) {

in ViewExecutable::render()

This adds at least 90 seconds to the test run, so this SHOULDN'T go in until #22 is done.
Apparently, the branch tests got slower too? They're up to 7.5 minutes...

aspilicious’s picture

Status: Needs review » Needs work
+++ b/lib/Drupal/views/Plugin/views/area/AreaPluginBase.phpundefined
@@ -97,6 +97,15 @@ public function buildOptionsForm(&$form, &$form_state) {
+   * Perform any operations needed before full rendering.

Performs

dawehner’s picture

Status: Needs work » Needs review
FileSize
34.6 KB

Fixed #26

This is currently in the state work in progress.

Things which are known to be broken

  • Blocks aren't rendered (because you can't simply return a render array, but you have to use block['content']/subject (that's an easy one)
  • views_get_page_view doesn't work in hook_page_alter, so contextual links don't work on page views
  • Caching of css/js was broken the last time i runned the tests, probably just some reference problem

The positive part: At least late rendering of the page view works fine.

Status: Needs review » Needs work

The last submitted patch, views-1811828-27.patch, failed testing.

tim.plunkett’s picture

+++ b/lib/Drupal/views/Plugin/views/area/Title.phpundefined
@@ -46,14 +46,12 @@ class Title extends AreaPluginBase {
+  function preRender(array $results) {

This should be public now

+++ b/lib/Drupal/views/ViewExecutable.phpundefined
@@ -2141,6 +2165,15 @@ class ViewExecutable {
+    $this->element =& $element;

= &$element, please

+++ b/lib/Views/block/Plugin/views/display/Block.phpundefined
@@ -75,12 +75,13 @@ class Block extends DisplayPluginBase {
-    $info['content'] = $this->view->render();
-    $info['subject'] = filter_xss_admin($this->view->getTitle());
+    $output = $this->view->render();
+    $this->view->element['#title'] = filter_xss_admin($this->view->getTitle());
     if (!empty($this->view->result) || $this->getOption('empty') || !empty($this->view->style_plugin->definition['even empty'])) {
-      return $info;
+      return $output;

This should just be reverted

+++ b/views.moduleundefined
@@ -144,6 +144,15 @@ function views_element_info() {
+  $types['view_special_block'] = array(
+    '#theme_wrappers' => array('container'),

Whaaaaat?

+++ b/views.moduleundefined
@@ -152,17 +161,38 @@ function views_element_info() {
+  dsm(123);

dpm() ;)

+++ b/views.moduleundefined
@@ -152,17 +161,38 @@ function views_element_info() {
+  $view->setElement($element);

Okay, that's kinda cool

+++ b/views.moduleundefined
@@ -152,17 +161,38 @@ function views_element_info() {
-    $element['view']['#markup'] = $view->preview($element['#display_id'], $element['#arguments']);
+    if ($element['#embed']) {
+      $element['view']['#markup'] = $view->preview($element['#display_id'], $element['#arguments']);
+    }
+    else {
+      $element['view']['#markup'] = $view->executeDisplay($element['#display_id'], $element['#arguments']);

I don't understand this

+++ b/views.moduleundefined
@@ -631,10 +661,17 @@ function views_page($name, $display_id) {
+  $element = array(
+    '#type' => 'view',
+    '#name' => $name,
+    '#arguments' => $args,
+    '#display_id' => $display_id,
+    '#embed' => FALSE,
+  );
+
+  return $element;

I think this

+++ b/views.moduleundefined
@@ -780,20 +817,15 @@ function views_block_view($delta) {
+    $element = array(
+      '#type' => 'view_special_block',
+      '#name' => $name,
+      '#display_id' => $display_id,
+      '#special_block_type' => $type,
+      '#pre_render' => array('views_pre_render_view_element', 'views_add_block_contextual_links'),
+      '#block_type' => 'special_block_' . $type,
+    );
+    return $element;

Except all of the weird "special_block" parts, I like this too

+++ b/views.moduleundefined
@@ -802,49 +834,37 @@ function views_block_view($delta) {
+  $element = array(
+    '#type' => 'view',
+    '#name' => $name,
+    '#display_id' => $display_id,
+    '#pre_render' => array('views_pre_render_view_element', 'views_add_block_contextual_links'),
+    '#block_type' => 'block',
+    '#embed' => FALSE,

What's with the views_pre_render_view_element? Why isn't that part of the regular #type => view stuff?

+++ b/views.moduleundefined
@@ -802,49 +834,37 @@ function views_block_view($delta) {
+  if (!empty($element['view']['#markup'])) {

This could use a comment.

dawehner’s picture

Status: Needs work » Needs review
FileSize
34.98 KB

I really tried hard but it seems to be impossible for me to either have title on blocks or pages,
but beside of that it's working pretty fine, so if someone has an idea, i would like to hear it!

@tim
Thanks for your long feedback, i corrected all of them, added comments or we figured out on IRC earlier.

Status: Needs review » Needs work

The last submitted patch, views-1811828-30.patch, failed testing.

xjm’s picture

Project: Views (for Drupal 7) » Drupal core
Version: 8.x-3.x-dev » 8.x-dev
Component: Code » views.module
dawehner’s picture

Status: Needs work » Needs review
FileSize
36.58 KB

Here is a rerole against 8.x

The reason why drupal_set_title doesn't work in Page.php is because template_process_page which loads the page title is executed before the page.tpl.php which
calls render($page['content']) which actually executes the view.

Status: Needs review » Needs work

The last submitted patch, views-1811828-30.patch, failed testing.

moshe weitzman’s picture

Oh right. For now, I think we give up on late render and just do late themeing. We still get the advantage of properly using #attached. Is this doable?

dawehner’s picture

Sure, but you will simply not being able to do caching as it is supposed to be, but sure this is still an improvement.
I will work on that tomorrow.

dawehner’s picture

Status: Needs work » Needs review
FileSize
26.33 KB

Finally managed to get it running without breaking again. PUH!

Status: Needs review » Needs work

The last submitted patch, drupal-1811828-37.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
27.43 KB

I'm really not sure why the CacheTest does break, but maybe someone else has an actual idea.

Status: Needs review » Needs work

The last submitted patch, drupal-1811828-39.patch, failed testing.

moshe weitzman’s picture

Code looks good to me. I dont know much about the test fails other than it would be good if they went away :)

catch’s picture

Priority: Normal » Major

Bumping to major, the diffing of the global CSS before and after is really fragile, and it's going to be completely incompatible if we end up doing subrequests, and/or if drupal_add_css()/js() get refactored significantly (which seems likely at the moment).

The patch is collecting things from #attached, but it's not removing the handling of the globals yet?

dawehner’s picture

Priority: Major » Normal

Linked a new issue which came out of the discussion with catch: #1830588: [META] remove drupal_set_title() and drupal_get_title()

dawehner’s picture

Priority: Normal » Major

ups.

moshe weitzman’s picture

Nice. We could also add ['#attached']['#block-title'] and that would solve our other problem. #block-title would be consulted when processing a top level block item.

dawehner’s picture

Status: Needs work » Needs review
Issue tags: -VDC

#39: drupal-1811828-39.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +VDC

The last submitted patch, drupal-1811828-39.patch, failed testing.

nod_’s picture

Issue tags: +JavaScript

tag

dawehner’s picture

Status: Needs work » Needs review
FileSize
27.03 KB
4.47 KB

This is not the full interdiff due to

$ interdiff drupal-1811828-39.patch drupal-1811828-49.patch > interdiff.txt
1 out of 5 hunks FAILED -- saving rejects to file /tmp/interdiff-1.RvbKpQ.rej
interdiff: Error applying patch1 to reconstructed file

but hey this passes the tests!

Status: Needs review » Needs work

The last submitted patch, drupal-1811828-49.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
27.07 KB

Let's fix the notices.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Unless @dawehner wanted to do more with this, I'm very very happy with this. Let's get it in!

dawehner’s picture

Once that part is in, i would like to work on other issues which will allow lazy-execution of a view.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.phpundefined
@@ -230,8 +230,11 @@ function post_render(&$output) { }
+    $this->storage['css'] = drupal_map_assoc(array_keys(drupal_add_css()));
+
+    $js_start = drupal_add_js();
+    $this->storage['js'] = drupal_map_assoc(array_keys($js_start));
+    $this->storage['js']['settings'] = isset($js_start['settings']) ? $js_start['settings'] : array();

I'm confused about this bit. If we're using #attached, then we can assume that everyone else is. If contrib or custom code isn't using #attached to add css or js for context-specific stuff then it's broken - especially since we'll be doing our best to eliminate all calls to drupal_add_*() if not the functions themselves this release.

Rest of the patch looks good though - is it easy to remove the global CSS/JS handling or should that be a follow-up?

dawehner’s picture

@catch

Once we have a proper rendering, like #1811828-33: Use #attached to find the css/js of a view tried to do, we don't need global css handling,
but until then it seems to be, that we sadly still need this kind of behavior, see #1811828-7: Use #attached to find the css/js of a view.
I'm happy with getting the other blockers into to be able to convert views to late executing.

catch’s picture

The comment in #7 is talking about node teasers. If you have node teasers, those are renderable arrays, which can have #attached set, and there's functions in common.inc to get the #attached from nested render arrays.

moshe weitzman’s picture

#51: drupal-1811828-51.patch queued for re-testing.

dawehner’s picture

Category: task » feature

Regarding #56
Not sure how we continue here, because to rid of the global handling we end up
with the render element, which is blocked at the moment.

I think the current patch is a step forward and will allow further improvements later.

dawehner’s picture

Category: feature » task

Hey!

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

I agree.

sun’s picture

I think it's perfectly fine to make progress in smaller steps.

However, let's make sure to create a follow-up issue then.
(Alternatively, we could also keep this issue open, but that most often turned out to be not a good idea.)

dawehner’s picture

webchick’s picture

Assigned: dawehner » catch

Catch seems to have his fingers in this particular pie.

catch’s picture

I don't understand why #1849822: Convert (HTML) view rendering to a render array affects the global js/css diffing, that seems to be about making the view itself return a render array?

dawehner’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
3.05 KB
27.8 KB

After a short talk with catch i think this is the way to go: kill a lot of code.

moshe weitzman’s picture

Nice work.

+++ b/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php
@@ -222,7 +222,7 @@ function cache_flush() {
   /**
-   * Start caching javascript, css and other out of band info.
+   * Start caching the head.

It isn't too clear what 'the head' means

+++ b/core/modules/views/theme/theme.inc
@@ -68,13 +72,14 @@ function template_preprocess_views_view(&$vars) {
 
-  $empty = empty($vars['rows']);
+  // Render the rows render array to check for contents.
+  $rows = $vars['rows'];
+  $rows = drupal_render($rows);
+  $empty = empty($rows);

index 8e645a6..2bbb507 100644
--- a/core/modules/views/theme/views-view.tpl.php

I see that we call render($rows) in the views-view.tpl.php so i'm surprised to see a drupal_render() call in preprocess as well. Ideally we render within the template and not in preprocess.

tim.plunkett’s picture

@moshe, to answer your second point, we make a copy of the row values, and render that, to see if they're empty or not.
The original values are not rendered, and passed along.

catch’s picture

Status: Needs review » Needs work
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.phpundefined
@@ -246,22 +244,9 @@ function gather_headers() {
-    // Slightly less simple for CSS:
-    $css = drupal_add_css();
-    $css_start = isset($this->storage['css']) ? $this->storage['css'] : array();
-    $this->storage['css'] = array_diff_assoc($css, $css_start);
-
-    // Get javascript after/before views renders.
-    $js = drupal_add_js();
-    $js_start = isset($this->storage['js']) ? $this->storage['js'] : array();
-    // If there are any differences between the old and the new javascript then
-    // store them to be added later.
-    $this->storage['js'] = array_diff_assoc($js, $js_start);
-
-    // Special case the settings key and get the difference of the data.
-    $settings = isset($js['settings']['data']) ? $js['settings']['data'] : array();
-    $settings_start = isset($js_start['settings']['data']) ? $js_start['settings']['data'] : array();
-    $this->storage['js']['settings'] = array_diff_assoc($settings, $settings_start);
+    $attached = drupal_render_collect_attached($this->storage['output']);
+    $this->storage['css'] = $attached['css'];
+    $this->storage['js'] = $attached['js'];
   }

Yes this is more like it!

catch’s picture

Status: Needs work » Needs review
moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

I think we are ready.

catch’s picture

Sorry one more question. Why would the hook_library_info() implementation depend on the configuration setting for js or not? I'd think we'd check that setting when actually attaching the js? hook_library_info() isn't cached, but there's an issue to do that, and if it was then changing the configuration setting would require invalidating that cache to avoid a missing library.

dawehner’s picture

Status: Reviewed & tested by the community » Needs work

Working on fixing the comment in #71

dawehner’s picture

Status: Needs work » Needs review
FileSize
4.3 KB
28.82 KB

nod_ suggested to add proper requirements to the hook_library entries

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC

catch’s picture

Issue tags: -JavaScript, -VDC

#73: drupal-1811828-73.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work
Issue tags: +JavaScript, +VDC

The last submitted patch, drupal-1811828-73.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
28.86 KB

Conflicted with #1818450: Introduce core token support in PluginBase class, straight reroll. Wait for green.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for the re-roll. Committed/pushed to 8.x.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

Dave Reid’s picture

An issue that was marked a duplicate of this was set to be backported to Drupal 7. I'm looking at how I can add #attached information to a view render and wondering if this in fact needs to be backported?

Dave Reid’s picture

Filed new issue #1894736: Cannot use #attached to add general CSS/JS/Library to a View to discuss so I won't ping this issue anymore.