So for awhile now, I have really ponder on how to cache only certain CTool modals display. For instance, I have wanted to create a CTools Modal for zooming in on images and I would like it to respond right away. So when I click it, I don't want to wait for an AJAX request, I want it now.

Same thing for login forms, when I click sign in, I would really like the form to be displayed right away. Modern websites accomplish this by putting the form in the markup and just hiding it until button click.

This patch, accomplishes these goals without using the markup. It does a fetch when Drupal.attachBehaviors is run and creates a cache in the browser. (Drupal.CTools.Modal.Cache).

So the response moves really fast on any a.ctools-use-modal-cache.

For whatever reason the following code failed on the .ajax request

/**
   * Click() function for modals that can be cached.
   */
  Drupal.CTools.Modal.clickAjaxCacheLink = function () {
    var position = $(this).attr('rel');
    if (position.length != 0 && Drupal.CTools.Modal.Cache[position]) {
      Drupal.CTools.Modal.show();
      Drupal.CTools.AJAX.respond(Drupal.CTools.Modal.Cache[position]);
      return false;
    }
    else {
       // HERE WE FAIL WITH UNDEFINED ERROR
       Drupal.CTools.Modal.clickAjaxLink.apply(this)
    }
  };

Not sure as to why, so I just copied that function into the else (see patch).

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

merlinofchaos’s picture

I! Love! This!

I would like to see this a little more generic. It seems like this could be used in all kinds of places that aren't the modal. For example, let's say I'm using the AJAX framework to write an image browser, where I show X images and have left and right buttons that use AJAX to fetch the next.

It seems like it would be very simple to warm the cache with the commands array, but only execute it when the button is actually clicked. It would be nearly the same code, I think and so could possibly be genericized?

It could have a massive, positive performance impact on many tools...for no cost to developers whatsoever.

Scott Reynolds’s picture

FileSize
2.59 KB

This time, tested patch with more then one ctools-use-modal-cache link and realized a minor error with the .each.

Scott Reynolds’s picture

Title: CTools Modal Cache » CTools Command Response Cache
FileSize
3.5 KB

ok so here is the generalization for any ctools command. I have used it and tested it with both modal and non modal commands. I have to tell ya, its sexy.....

Scott Reynolds’s picture

Been think aobut it some more. Perhaps we create a jquery event ('ctools-cached'). Then in warm cache, it fetches the result and fires off that event.

This then allows the click functions to do

if (hasClass(ctools-fetching) {
  $(this).bind('ctools-cached', respond)
}

That way we don't hit the server twice for the same link (one when its trying to cache, and the other when its not cached yet but user clicked).

Scott Reynolds’s picture

FileSize
3.9 KB

Ok so followed up on my last comment. What the real point of that change is to prevent a user from clicking a cachable link before the result has been cached. Thus creating TWO hits on the url, one for the cache warming and one for the user click.

This patch addresses that point, when a user clicks and its ctools-fetching, it binds to the ctools-cache-warm event to render the display. Thus waiting for the cache result to return.

Scott Reynolds’s picture

FileSize
3.89 KB

tiny code style change (4 spaces instead of 2 on one of the lines.)

m3avrck’s picture

This is looking great! However, if you have 3 links on the page that all goto a login form, you will fire off 3 of the same AJAX requests:
http://img.skitch.com/20100119-ku8xd4nfcsnwpm277u3r1ukicf.jpg

I think a way around it would be a static cache within warmCache():

var cache = new Array();
...
try {
  url =
  if (cache[url]) {
    // run the success logic
  }
  else {
    $.ajax...
      success:
        cache[url] = data

What would make this even killer was if it was smart enough to run a single AJAX request even if the destination was different, and then be smart enough to fix the response with the correct destination.

m3avrck’s picture

Sorry this should be updated to use an object cache, not an array cache. But same psuedo-code still applies.

Scott Reynolds’s picture

Status: Needs review » Needs work

I agree, instead of an Array() we use an Object(). and then we can do .commandCache[url] = data.

then we have to manage a set of links, triggering ctools-warm-cache on all of them that match as well as setting ctools-fetching on all the ones that do match but didn't init the ajax call.

Some management that can go in there, that while, complicated is totally doable and desirable.

Scott Reynolds’s picture

Status: Needs work » Needs review
FileSize
4.05 KB

So this next installment :-D, achieves the following.
For three links that goto user/login/nojs?destination=user, it does exactly one request to that url and this is done via cache warming. If the user clicks the link before the cache is warm, it still waits until the cache is warm, displaying only the loading modal for instance.

It achieves m3avrck's goals, minus the destination part. And thats only because, the get parameters could really determine a different response not just another additional ctools command. Take pagers for example, a ?page=2 will return completely different markup then ?page=4. It would be undesirable to try and determine that at the javascript layer.

But for links like that where you know that on your site user/login/nojs?destination=X, you could write your own behavior for those links and have it the cache warming only once for them. Just requires some creativity.

Small note about my coding style for variables, I use $STRING to represent jQuery objects. if you object to that, then I can rework that. Its just a small short hand I use and I find it helpful.

m3avrck’s picture

Does: var $objects = $('a[href=' + old_url + ']') need to refind the same link? Won't this actually just be $this instead of an expensive selector?

Also, in the $.ajax, can you try adding cache: true? IIRC, jQuery doesn't cache JSON by default. In this case, I would imagine it could be cached locally in browser, so subsequent hits are even faster.

Scott Reynolds’s picture

Does: var $objects = $('a[href=' + old_url + ']') need to refind the same link?

Its not finding the same link. Its finding all links like it (hence plural). So it finds all user/login/nojs?destination=user, and that includes $this, but its also is every other link.

IIRC, jQuery doesn't cache JSON by default

ahh you sure? looking at http://api.jquery.com/jQuery.ajax/, it says "Default true, false for dataType 'script' and 'jsonp'". Now I think that site is for jquery 1.4, so maybe its changed?

m3avrck’s picture

Ok maybe add a comment that is finding duplicates? Forgot about that :)

You would need to check the cache headers from AJAX response.

For clarity, it might be better to explicitly state cache: true

Scott Reynolds’s picture

FileSize
4.24 KB

Ok I agree with m3avrck that we should add a comment about 'duplicates'. If he couldn't understand what that line was doing and he had explicitly asked for that then clearly it need some comments

added

    // Grab all the links that match this url and add the fetching class.
    // This allows the caching system to grab each url once and only once
    // instead of grabbing the url once per <a>.
   var $objects = $('a[href=' + old_url + ']');

I think that makes it clear.

merlinofchaos’s picture

Title: CTools Command Response Cache » [needs documentation] CTools Command Response Cache
Status: Needs review » Active

Ok, I went ahead and committed this. I rearranged the binding slightly so that it's safe to use ctools-use-ajax and ctools-use-ajax-cache together (previously you would've gotten ugly errors). You still don't really want to do this, but it won't cause errors now, I think.

The use of this should be documented along with the ajax documentation. Though there's a bigger project there too, which is that we may want to go look up all the nice documentation that got written for core when this went into D7, and backport it into CTools documentation (with a comparison of what's different in D7 which may help people in their porting).

merlinofchaos’s picture

Oh one other thing. We probably want to port the cache warming part to D7, but that's going to basically be its own thing since most of the AJAX stuff is in core in D7.

merlinofchaos’s picture

Category: feature » task
japerry’s picture

Status: Active » Closed (outdated)

Closing this issue as outdated as Drupal 6 ctools is not supported. If this issue is relevant for Drupal 7, feel free to re-open and mark for Drupal 7 (or 8)