Problem/Motivation

It is not possible to combine caching on a view with Ajax and page caching.

Steps to reproduce, which require two web browsers:

  1. Create a view with exposed filters.
  2. Enable caching and Ajax on the view.
  3. Enable page caching for anonymous users then log off (so page caching is active).
  4. In the first web browser, browse the view, use the exposed filters. They should work.
  5. In the second web browser, browse the view, use the exposed filters. They should work.
  6. In the first web browser, log in and clear the caches. Log out.
  7. In the second web browser, browse the view, use the exposed filters. They may work.
  8. In the first web browser, browse the view, refresh the page, then try the exposed filters. They should not work.

The reason this happens is that in step 7, the second web browser causes cached view data to contain the old views ID class for the view div. In step 8, the first web browser receives HTML with the old views ID class and puts it in the DOM. Thereafter, Ajax commands no longer work because they cannot find the view in the DOM, being that they are now looking for the new ID.

Proposed resolution

Do not trust the class ID sent to views_ajax().

Remaining tasks

User interface changes

API changes

Data model changes

Original Report

I'm really not sure if this is a bug or not - but I could really use some help figuring that out.

I came across this issue while working on a patch for Quicktabs (#1864800: Ability to force AJAX fetch of tab content every time a tab is toggled). The object was to have the tabs reload via AJAX whenever they were toggled (not just once). Quicktabs can be set to render views as tab content.

The issue above provides an outline of the problems that I encountered with Views. Basically, once a View to reloaded via AJAX, the DOM ID changes, and the JS settings contain both the new and old ID (http://drupal.org/node/1864800#comment-6861108). Thus, the View's own AJAX does nothing (it submits, but nothing at all is updated). We also receive the same error from this issue: #1809958: Views with exposed filter (ajax enabled) inside modal window (ctools) (the cause may be related)

Here are some other possibly-related issues out there:

So, again, I'm not sure if this is a bug exactly, but I'd love to hear what you guys think.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mstef’s picture

The error from #1809958: Views with exposed filter (ajax enabled) inside modal window (ctools) is thrown because Views is using the old selector still in the JS settings, but it doesn't exist any more.

mstef’s picture

As a workaround/hack, I had to manually delete from Drupal.settings.views.ajaxViews[] before reloading the View via an AJAX call.

Unfortunately, the bug from the above-mentioned issue still gets thrown regardless, because Views seems to randomly swap in a new DOM ID after N filter submits (can't find a pattern).

So - we have 2 separate issues to resolve.

Danny Englander’s picture

I am getting this exact error just with a clean install with Views, an exposed filter and ajax enabled with the view in a block. Nothing fancy and no other modules involved. Not sure why, I set up these types of basic views all the time. I also noticed that Chrome web inspector throws this error:

Uncaught TypeError: Cannot read property 'top' of null

(see below)

Danny Englander’s picture

I spoke too soon, it turned out to be an issue with my theme where I had the Views block in a custom node block using a theme preprocess function. Once I moved the block to a standrad region within page.tpl.php, it works fine, my bad.

seanmrafferty’s picture

Status: Active » Needs review
FileSize
582 bytes

Unfortunate hack to get around this problem.

safshar’s picture

Was anybody able to resolve the issue. My issue isn't just the scroll-top on the Ajax paging. My view has exposed filters and after the submission, seems like the DOM ID has changed and once I alternate between ajax paging and filters, the 'Offset' variable is undefined. I do have a custom template for the view and embedding two views on that page which have different pager ID(s).
Thanks!

safshar’s picture

Issue summary: View changes
Status: Needs review » Needs work
kmoll’s picture

I have also experienced this and spent some time debugging. I launch a modal from node form. During the pre-processing of the view (in theme.inc) js settings get added to the view. If you close the modal and re-open it, it adds the js settings again. Now there are two Drupal.settings.views.ajaxView objects.

I was able to implement a work around where I added a click event to the close button to remove the ajaxView settings. But this is just a workaround. I think the real problem lies in the fact that the ajaxView settings do not get removed when the modal closes.

heger245’s picture

I am running in the same issue, my view block is inside a node content via a preprocess function. On Ajax Refresh the view fails with this js error. Is there anyone who has a workaround for my situation?

glass.dimly’s picture

Hey all.

I was getting "Uncaught TypeError: Cannot read property '$el' of undefined." I had added a flipClock from flipclockjs.com. It's a pretty baller countdown clock, actually. Everything worked great in Firefox. Firefox is a javascript bull, don't stop for nothin'. But Chrome was choking and nobody could add items to their cart (bad).

This is how I fixed my error. I was getting the error on submit of Commerce add to cart form. I had added a custom object via Drupal behaviors like so:

(function ($) {
  Drupal.behaviors.addflipclock = {
    attach: function (context, settings) {
      //my amazing object being loaded here
    }
  };
})(jQuery);

The problem was, this was getting added again on ajax refresh. Drupal behaviors runs again on ajax refresh. So what I needed was a regular old jQuery document ready function:

jQuery(document).ready(function($){
     //my amazing object being loaded here, except only once per page load.
  }
})(jQuery);

Alright, hope that was useful to somebody, sometime, somewhere.

khoatm’s picture

@glass.dimly: It still doesnt work in IE.

geoffreyr’s picture

I've put together an alternative patch that checks for the existence of the element to scroll to before we attempt to scroll to it. If it doesn't exist, nothing happens.

Am wondering if we should always ensure that the viewsScrollTop command should always come after any insert commands created by views, maybe I should take a look at that as well...

cilefen’s picture

I have looked into this quite a bit and have found that the HTML returned from Ajax contains the wrong ID, and the expected "correct" ID is passed to command viewsScrollTop, which no longer exists in the DOM. I can get this consistently if the Ajax rendered data or query are cached.

cilefen’s picture

Here is an example of some commands that Ajax returns when it has broken:

{
    "command": "viewsScrollTop",
    "selector": ".view-dom-id-2e9403d39aae5f8b54fac491ea8a390b"
}, {
    "command": "insert",
    "method": "replaceWith",
    "selector": ".view-dom-id-2e9403d39aae5f8b54fac491ea8a390b",
    "data": "\u003Cdiv class=\u0022view view-ideas-landing view-id-ideas_landing view-display-id-block_1 view-dom-id-e243b89b17c18ee323ccec837191e53b (...)",
    "settings": null
}

I trimmed down the data key but the main thing to notice is are the different view-dom-id-* classes referenced, view-dom-id-2e9403d39aae5f8b54fac491ea8a390b selectors and view-dom-id-e243b89b17c18ee323ccec837191e53b in the HTML that is replaced. After this runs once, the selectors will never work again.

cilefen’s picture

Issue tags: +Ajax
cilefen’s picture

I've traced it so far to views_ajax():

$commands[] = ajax_command_replace('.view-dom-id-' . $dom_id, $view->preview($display_id, $args));

This is the DOM replacement command for the view. At this stage, $dom_id is correct but the ID in the HTML returned by $view->preview() is not, so that's the next place to look.

cilefen’s picture

Title: Views DOM ID and JS Settings conflict after AJAX reload » Views caching cannot be combined with Ajax on cached pages
Priority: Normal » Major
Issue summary: View changes
Status: Needs work » Active
cilefen’s picture

Issue summary: View changes
MustangGB’s picture

Status: Active » Needs review
FileSize
675 bytes

Possibly not the end-game solution (or maybe it is), but this did the job for me.

MustangGB’s picture

FileSize
675 bytes

Actually it turns out "\S" is a bit too inclusive if it appears last in the list of classes, here's a manually edited patch with "\w" instead.

cilefen’s picture

I am testing this out. That change is going to need an inline comment of explanation.

cilefen’s picture

Status: Needs review » Needs work

+1 in general. The strategy in #20 that the view display's DOM ID on the page may be old and this ensures that its is not replaced with a new DOM ID, which breaks JavaScript.

I suggest this inline comment, or similar:

// Return HTML with the same DOM ID that was sent by the browser.

As for the regex replacement, it could suffer all the usual problems: mainly slowness. It may make sense to set the limit param of preg_repace() so as not to search the entire string. I think we need to replace just one instance (or two?).

Has anyone experimented with D8 to see if the bug is there?

cilefen’s picture

#20 is problematic for me on views with multiple exposed filter groups in blocks.

zalak.addweb’s picture

Issue tags: +views
MustangGB’s picture

Issue tags: -views
FileSize
678 bytes

#20 wasn't compatible with Views Field View, attached patch addresses this.

@cilefen: Not sure what you mean by "filter groups". Do you mean multiple exposed blocks for multiple views on the same page, or do you mean multiple exposed filters for a single view.

@sipra.patra: I don't see why we need a views tag for an issue in the views queue.

cilefen’s picture

@MustangGB: I mean the latter.

MustangGB’s picture

In that case I'm unable to reproduce as I'm using it with multiple exposed filters in a block and it's working.

firewaller’s picture

This may be a separate issue, but I'm working with Views Autorefresh and run into the problem where the cached view simply reloads the same cached version of itself, causing a useless refresh of redundant data.

I've gone ahead and added an additional parameter "bypass_cache" in views_ajax() to optionally bypass the view cache prior to previewing the display for the AJAX command.

See proposed changes in attached patch.

j.b’s picture

Hi,

We have the same problem on Drupal 8.

robertom’s picture

Status: Needs work » Reviewed & tested by the community

Tnx @MustangGB

patch #1877238-25: Views caching cannot be combined with Ajax on cached pages works well for me.

I'm unable to reproduce @cilefen error.

The last submitted patch, 12: 1877238-12-ensureoffset.patch, failed testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 28: views-caching-ajax -1877238-28.patch, failed testing.

robertom’s picture

Status: Needs work » Reviewed & tested by the community

I think patch #28 is unrelated to this issue.

I would remove patches #12 and #28 from display and reset the issue as RTBC

willzyx’s picture

The patch not works for me when used with views with exposed filter inside ctools modal window (Entity Reference View Widget)

+++ b/includes/ajax.inc
@@ -68,7 +68,7 @@ function views_ajax() {
+      $commands[] = ajax_command_replace('.view-dom-id-' . $dom_id, preg_replace('/view-dom-id-\w+/', 'view-dom-id-' . $view->dom_id, $view->preview($display_id, $args), 1));

the regex should take in account also - (minus sign) since the \w flag does not include it or otherwise a valid dom id can be replaced with an invalid one. In my case it transform
view-dom-id-MYVIEW-MYDISPLAY
in
view-dom-id-MYVIEW-MYDISPLAY-MYDISPLAY

Taking in account minus sign in the regex fix the problem for me

willzyx’s picture

joelpittet’s picture

Status: Needs review » Needs work

@willzyx is that an interdiff in #35? If not could you explain why the previous RTBC'd patch is not included in your patch?

willzyx’s picture

Status: Needs work » Needs review
FileSize
789 bytes

@joelpittet the previous RTBC'd patch was the patch in #25, as stated by @robertom (see #30) , and is included in the latest patch.

Anyway you are right.. here a one-line interdiff for a one-line patch :)
the only change that I've made is change the regex used for replace the DOM ID from '/view-dom-id-\w+/' to '/view-dom-id-[a-zA-Z0-9_-]+/' since, as explained in #34, the DOM ID can contains dashes (here the proof) and the previous patch doesn't takes in account this

narkoff’s picture

#35 resolved issue for me. I have not found any issues so far.

joelpittet’s picture

Status: Needs review » Reviewed & tested by the community

@willzyx my confusion I was looking at the larger last patch in #28 by accident.

The proof link was very helpful!

MustangGB’s picture

Looks fine to me.

cilefen’s picture

Out of laziness I have not tried reproducing this on Drupal 8. But in #29 someone wrote that they did. Has anyone else been able to do so? If so we will need a D8 core issue opened.

joelpittet’s picture

@cilefen, looks like the same thing in D8, created a clone: #2895446: Views caching cannot be combined with Ajax on cached pages

cilefen’s picture

Thank you for investigating.

MustangGB’s picture

  • DamienMcKenna committed 304108e on 7.x-3.x
    Issue #1877238 by MustangGB, willzyx, seanmrafferty, firewaller,...
DamienMcKenna’s picture

Status: Reviewed & tested by the community » Fixed

Committed. Thank you all.

Status: Fixed » Closed (fixed)

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