Block caching prevents the javascript from always being loaded
George2 - March 17, 2009 - 15:26
| Project: | Views Slideshow |
| Version: | 6.x-2.x-dev |
| Component: | Code |
| Category: | bug report |
| Priority: | critical |
| Assigned: | Unassigned |
| Status: | needs review |
Description
I have a block view which if i enable block caching with, the slideshow no longer works - i just see a normal ul / li type listing.
is there a way to get caching working with block views and views_slideshow?
thanks

#1
Bumping to 2.x
#2
This happened to a site of mine.
The views slideshow javascript files are not being included when a block is cached.
#3
[oops, double post]
#4
Block caching and js seems to be a known problem in drupal. Look at this from fivestar. http://drupal.org/node/364764
marking as won't fix as this seems to be a drupal problem and not a views_slideshow issue.
#5
I would disagree with the decision to mark this as won't fix, as it severely limits the ability for a person to use the module. While many of us recognize why we might not want to load the slideshow JS on every page, on a high-performance site, disabling the cache for a slideshow block can be worse.
Do you have any specific opposition to using a solution that is similar to the fivestar solution?
#6
I'm not sure that loading js on every page is the best approach for this module. In 2.x you can have x number of plugins. For each plugin you enable we would have to load a js file on every page no matter if views slideshow is used or not. Another option for a user is to add the js files to their theme. This will load it on every page load to accomplish the same effect.
There is a patch that needs work in the drupal queue to fix this situation. #235673: Changes to block caching mode not caught
Do you think this is a reasonable solution?
#7
Although a quick glance at the linked to issue queue didn't directly tell me how these issues were related, I guess adding it to the theme layer is a reasonable solution. My biggest concern here is adding the settings to the jQuery drupal options array. As it is right now, it isn't as simple as copy and pasting the module code into the theme/hook_init() of a custom module.
#8
It turns out that it is a lot of work to hardcode the javascript into a custom module via hook_init. You'll notice that I had to hardcode a lot of data for performance reasons (I did not want to unnecessarily load the views object every time a user loaded the homepage).
This is my hook_init code
function MODULE_init() {
// code for views_slideshow to prevent the caching bug
// Most of this is copied from template_preprocess_views_slideshow_singleframe
// in contrib/views_slideshow_singleframe/views_slideshow_singleframe.theme.inc
// only display on the front page
if ($_GET['q'] == 'front') {
// Add support for the jQuery Cycle plugin.
// If we have the jQ module installed, use that to add the Cycle plugin if possible.
// That allows for version control.
if (module_exists('jq')) {
$js = jq_add('cycle');
}
// Otherwise, we'll add the version included with this module.
if (!$js) {
// Disabled JS aggregation for this module's JS since it is only loaded
// on one page, we don't want to force users to have to load TWO
// aggregated JS files across the site
drupal_add_js(drupal_get_path('module', 'views_slideshow') .'/js/jquery.cycle.all.min.js', 'module', 'header', FALSE, TRUE, FALSE);
}
$base = drupal_get_path('module', 'views_slideshow_singleframe');
drupal_add_js($base .'/views_slideshow.js', 'module', 'header', FALSE, TRUE, FALSE);
drupal_add_css($base .'/views_slideshow.css', 'module');
// Unfortunately we have to hardcode the number of divs (number of items)
$num_divs = 3;
// hardcoding the view id
$view_id = 1;
// these are the hardcoded $options from views. If we change the settings,
// they must be changed here. This is the best way (for performance)
$options = array (
'timeout' => '4000',
'sort' => '1',
'effect' => 'scrollRight',
'speed' => '700',
'random' => '0',
'pause' => '1',
'controls' => '0',
'pager' => '1',
'sync' => '1',
);
$settings = array_merge(
array(
'num_divs' => $num_divs,
'hoverFunction' => NULL,
'id_prefix' => '#views_slideshow_main_',
'div_prefix' => '#views_slideshow_div_',
'id' => $view_id,
),
$options
);
// load the static $javascript var
$javascript = drupal_add_js();
$loaded = FALSE;
// this should never return true
foreach ($javascript['setting'] as $setting_array) {
if(array_key_exists('viewsSlideshowSingleFrame', $setting_array)) {
$loaded = TRUE;
}
}
unset($setting_array);
if ($loaded == FALSE) {
drupal_add_js(array('viewsSlideshowSingleFrame' => array('#views_slideshow_main_'. $view_id => $settings)), 'setting');
}
}
}
I also noticed a bug with this approach. Basically, if the block cache was cleared, it would load the settings twice, which broke the jQuery from working properly. For this reason, I had to create this (attached) patch for views_slideshow_singleframe.theme.inc
I am not necessarily saying this is the best solution, but if we want to avoid loading the JS on *every* page by default (which is reasonable), this seems to be the only way. The patch does not seem to be that burdensome and allows people to use the module on high-performance sites.
#9
Hmm I thought drupal_add_js() handled duplicate loading.
#10
It does, but you can also load the contents from the static $javascript variable by the method I used (so we can ensure the settings have not already been added to the array).
more info: http://us2.php.net/manual/en/language.variables.scope.php
#11
@kmonty: What happens if you don't do it this way?
I'm using hook_init to add the js and it seems to be working fine so far. What problems might I run into?
function lntw_tweaks_init() {// Add JS from Views Slideshow to every page.
// This is necessary so Views Slideshow blocks will work correctly for anonymous users when
// page caching is turned on.
drupal_add_js(drupal_get_path('module', 'views_slideshow') .'/js/jquery.cycle.all.min.js', 'module');
drupal_add_js(drupal_get_path('module', 'views_slideshow') .'/js/views_slideshow.js', 'module');
}
#12
@adamo #11
That solution does not work because it does not load the settings in the Drupal settings array. Without the settings loaded, the slideshow will not work (it does not know what divs to target).
#13
@redndahead #9
I misread your comment. It handles duplicate loading of .js files, but not settings. You can look at the function on API.d.o and see this.
Any thoughts about committing this patch?
#14
@kmonty I'm on the fence about it mainly because I don't quite grasp it yet. I am planning on release a beta 2 today and I don't think this one will get in, but it is one of my priorities before a full release. If someone else can corroborate that this works for them that would go a long way.
#15
Also at a quick glance it seems it wouldn't load the second set of settings if there were 2 singleframe slideshows on one page. I haven't tested do you that might be the case?
#16
This is the case:
blockcaching enabled and views caching (both query and output caching) turned on.
Under this situation, the module will absolutely not work. Even with the code from comment #11, the views_slideshow settings will not be loaded into the Drupal.settings array unless you manually do it via a hook_init() (as I demonstrated by my code in #8).
The module as-is is fine with one exception: when the cache is cleared (by saving a node, for example), drupal will add the views_slideshow settings to Drupal.settings twice, once from the custom hook_init() and once from the template_preprocess_views_slide() function provided by the module. The first page view (that generates the cache), the slideshow will not function due to the duplicate settings. Subsequent page loads will function correctly, as the template_preprocess_views_slide() function is not called.
Re: #15
I have not tested this yet because it has not been a part of my use-case. One possible way to get around this error would be to tag the settings array not just by the standard "viewsSlideshowSingleFrame" but
"viewsSlideshowSingleFrame". $view_id. This way, when we get to array_key_exists(), the function will not return TRUE for additional slideshows.I am marking this as critical because it should be a release blocker as the issue prevents users from using this module on a performance-minded production site.
Please let me know if I am not clear.
#17
I've done some testing and this only seems to be a problem if you have Block Cache set to 'global'. If you set it to 'per page' it seems to work just fine. This is with full page cache, page compression, block cache, css/js optimization turned on. View level cache is not enabled because even the Views CSS wasn't getting loaded with that turned on. Please test on your end and confirm if you get the same results.
#18
I've verified this works for me. Is this a viable solution?
#19
Also if using Authcache you need to set block caching to "Per role, per page" for each slideshow block.
This works but it is really not ideal for performance especially when you have the same slideshow block appearing on every page for every user.
Loading the JS and settings for every views slideshow on every page is really not ideal either.
Would it be possible to embed the settings in the in a script tag in the view content? That way the settings would get cached along with the rest of the content?
Another way might be for Views Slideshow to determine what slideshow blocks appeared on a given page and load the JS and settings in hook_init(). Perhaps there could be a setting in the view... Could be a simple Load via hook_init() checkbox or could be something like the visibility settings in block configuration. Maybe the settings could be serialized and stored with variable_set() (or in your own table). The hook_init() code could check for the views_slideshow_init variable, and if present deserialize and load the settings. If the variable is empty nothing else would be done, so this shouldn't really have any impact on sites that don't need it. Deserializing and loading the settings on each page view would be better than rendering the entire block on each page view.
#20
I tested out my idea of embedding the settings JS in the view content and it works fine. I have 3 Views Slideshow blocks cached globally and full page cache, block cache, CSS aggregation, and JS aggregation turned on. The settings will get cached with the rest of the view content. You still need to make sure the Views Slideshow CSS and JS files are loaded on each page where the slideshows appear, but this is much easier than having to hardcode all of the settings in hook_init(). If you are using Views Slideshow in blocks, you know where all of those blocks are going to be loaded and can easily implement your own hook_init() to only load the necessary CSS and JS files on the appropriate pages.
I've only done this for the singleframe slideshow at this point. The changes are relatively minor...
In views_slideshow_singleframe.theme.inc, at the bottom of function template_preprocess_views_slideshow_singleframe() replace this:
<?phpdrupal_add_js(array('viewsSlideshowSingleFrame' => array('#views_slideshow_singleframe_main_'. $vars['id'] => $settings)), 'setting');
?>
with this:
<?php
/**
* Modified to embed settings in view content so they will get cached with the view content when
* block cache is turned on.
*/
//drupal_add_js(array('viewsSlideshowSingleFrame' => array('#views_slideshow_singleframe_main_'. $vars['id'] => $settings)), 'setting');
// For inline Javascript to validate as XHTML, all Javascript containing
// XHTML needs to be wrapped in CDATA. To make that backwards compatible
// with HTML 4, we need to comment out the CDATA-tag.
$embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
$embed_suffix = "\n//--><!]]>\n";
$vars['js'] = '<script type="text/javascript">' . $embed_prefix
.'if (Drupal.settings.viewsSlideshowSingleFrame == null) {Drupal.settings.viewsSlideshowSingleFrame = {};}'
.'Drupal.settings.viewsSlideshowSingleFrame["#views_slideshow_singleframe_main_'. $vars['id'] .'"] = '. drupal_to_js($settings) .';'
. $embed_suffix . "</script>\n";
?>
Then in views-slideshow-singleframe.tpl.php, put this at the beginning:
<?phpprint $js;
?>
I can do up a patch if you want.
#21
Adamo - Tried your fix and it didn't work for me. :(. Any other ideas?
#22
chalja is referring to this issue #610838: Slideshow block only displays immediately after cache is cleared
#23
I think this is a different issue. I've never seen the block just disappear. When the JS isn't loaded you end up with no animation and no prev/next/pause/resume buttons. When the CSS isn't loaded you end seeing all slides in a list. Neither of these conditions results in the block completely disappearing.
#24
Oh boy! With /everything/ disabled (caching/optimization, etc.), the slideshow STILL only displays immediately after i clear the cache manually. Thoughts? i'm not using Authcache, though i am using BlockCache Alter which i believe has the same functionality along these lines.
#25
The plot thickens!
When i use the default Garland theme, the slideshow block works!
It is only with my custom theme that i am experiencing this issue! Should i stop posting here due to the fact that it's probably some sort of theming error (even though the theme is very minimalistic and i can find none)?
#26
K guys - i have a possible fix for you (Anyhow, it worked for me!) Simply bypass all you've done here, and in your view, under the caching option, enable caching, for say, 1hour by 1 hour, and bam, everything works. Not joking. Maybe my problem was different from yours - but it's worth a shot, eh?
#27
@chalja: My guess is you still had some left over cache from moving around your caching options.
Oye caching is becoming a pain and my lack of knowledge isn't helping. So far I have about enough time to answer posts. I'll try to get to some of these patches soon.
#28
For what it's worth I don't think chalja's issues have anything at all to do with this issue.
I'll try to explain the situation and hopefully it'll be a little more clear.
Block caching is a problem because the code that adds the JS and CSS is called in the code that renders the block. When block caching is turned on, that code only gets executed when the block is initially rendered. The output of that block gets put in a blob in the cache_block table. The next time that block is loaded in a page Drupal just takes cached output from the cache_block table, and prints that in the page. Hence, drupal_add_js and drupal_add_css are not called when a block is loaded from cache, and the JS/CSS will not get added to the page.
Getting Drupal to load a couple of JS and CSS files in hook_init() on every page or just the pages where the slideshow blocks are displayed is pretty trivial. Having to hard code all of the JS settings for each slideshow block in hook_init() is not trivial.
The code I posted does pretty much the same thing that drupal_add_js does to load the JS settings into the page. The only difference is instead of it getting put in a script tag in the header of the page, it's getting put in a script tag in the block content. This gets cached along with the rest of the block content, so when Drupal loads the block content from the blob in the cache_block table, the script tag containing the code to add the appropriate settings to the Drupal.settings JS object is included in that blob. It gets executed inline when the page is loaded by the web browser, and the appropriate settings are all in the appropriate place when the code in views_slideshow.js is executed (after the entire page has been loaded).
On more thing... If you do it this way the patch that kmonty posted above isn't necessary. The settings are always loaded with the block content, so there is no chance of them getting duplicated whether or not the block has been cached.
Let me know if there's anything I should try to clarify further.
Thanks!
@chalja:
I have the view cached 1 hour/1 hour on my site, but that didn't do any good until I modified the module to put the settings JS in the view content. Those settings have to get loaded somewhere. Either it gets done when the view content is generated (which will not occur when it gets loaded from cache), or it can be hardcoded in hook_init (major pain), or it can be embedded in the view content and then it will be stored and loaded from cache with the rest of the view content. Have you tried viewing your block on a different page after it has been cached? If you have full page cache enabled the the JS will be loaded when the view content is initially generated and since it is there in the page it will get cached in the page. Next time you load that page the full page including all the JS/CSS will be loaded from cache. Then when you go to another page on the site it will load the cached view content, the code to add the JS and CSS to the page will never get called, so it will not be added to the page, and you'll end up with a broken slideshow on that page.
#29
@adamo thank you that really helps me understand, even at 12:30 am. :)
So let me see if I get this.
1) apply patch above
2) If you cache your blocks then you need to make sure that you are adding the js files on each page through your template.
Sound about right?
#30
Yep. Whether you are caching at the block level or view level or both, the code I posted above makes sure the settings for a slideshow are always loaded with that slideshow. You can add the JS/CSS files to your template or you can add them in your own module's implementation of hook_init().
#31
Seems I may be able to create an option to load the needed js files on hook_init(). Is it ok to load the files there with drupal_add_js()?
#32
Yep.
The only reason it's a problem using drupal_add_js within the view code is that it won't get called if the content is loaded from cache (it puts the JS in the page header, not in the view content). hook_init() runs on every page. It doesn't get run if the page is loaded from cache, but that's OK since the JS is included in the page header and the full page is being cached.
#33
Here is my first stab at it. It creates settings under admin/settings/views_slideshow. In those settings you'll be able to set if the javascript files should be loaded on every page.
Testing would be great. If more is needed it would be great to know.
#34
Forgot a file.
#35
I tried to test this out today but I wasn't able to apply the patch successfully. If you can post a zip or tar.gz of the source tree you created the patch from I can test it out right away. Otherwise I will make the changes manually and test when I have time to dig into it. Thanks!