CTools has a feature called 'cache-warming'. With this feature, properly marked AJAX calls can be made in the backgorund and the response held in a warm cache. Then, when the user actually triggers the ajax (typically by clicking on an item) the response is ready to go and the user does not have to wait. This is useful for scrolling slideshows and tabs where the response time could be a factor, without having to have the entirety of the HTML actually in the original download.

In order to accomplish this, CTools needs to change the actual click responder. However, in ajax.js, this is an anonymous function and cannot be changed out.

Recently we modified the 'options' array so that it is named, rather than a local variable, so that CTools could modify the URL late in the process. A similar change here converts the anonymous trigger response into a named function. By being named, this means that it can be replaced by a contrib module, allowing CTools to retain its cache-warming functionality without having to abandon the ajax object entirely.

Patch forthcoming.

Comments

merlinofchaos’s picture

Status: Active » Needs review
StatusFileSize
new4.08 KB

This patch should have absolutely zero apparent functionality change to core, other than the anonymous function being replaceable.

As a bonus, by naming the function I was forced to document it.

ksenzee’s picture

This looks good to me on visual inspection, but I need to step through and verify, and I haven't found time to do that yet. (In other words, subscribing.)

rfay’s picture

subscribe

effulgentsia’s picture

subscribe.

merlinofchaos’s picture

Bump. Can one of you give this a review? CTools is waiting on this to be able to implement the cache-warming feature properly.

rfay’s picture

Hi Earl - If you could just say a little about the general benefits of this, and what the risks might be, and how we can test it manually, I think we'll be more capable of taking it up. Right now it's just some code that ctools needs, and of course we all want ctools to have what it needs. But we don't want to break anything or cripple anything that now works.

Could you follow up on this a bit?

merlinofchaos’s picture

rfay: Did you read the initial post? I explained pretty thoroughly, I thought. Do I need to explain again or are there specific parts of my explanation I can clarify?

merlinofchaos’s picture

In answer to part two -- we can't test javascript that I know of, so I can't help there.

merlinofchaos’s picture

Let me clarify:

All this code is doing is replacing two anonymous functions with named functions. In terms of actual functionality change, there should be zero.

Anonymous functions are bad because they cannot be referenced. Once they are created, they just hang around (anonymously) and that's all you can do.

Named functions on the ajax object can be called externally and/or replaced.

Two benefits:

1) Someone wanting to use the ajax framework and the Drupal.ajax object can replace the event responder (i.e, the code that actually responds to the click event) with something else. As a back-of-the-napkin example:

var ajax = new Drupal.ajax(element_settings);
ajax.storedEventResponse = ajax.eventResponse;
ajax.eventResponse = function (trigger) {
  // do something additional
  return this.storedEventResponse(trigger);
}

You could use this to do something before or after the actual ajax call. You could use it to replace the guts of the ajax call with something else. Right now, it is very difficult to affect things. The actual use-case for cache warming is way too complicated to explain here, it would only confuse the actual issue.

2) Someone wanting to control the event that triggers the AJAX can do so by directly calling the object. Right now, I have example code (that I'm presenting Saturday and will post somewhere for people to peruse) that performs AJAX operations on a timer. It works, but it's mildly annoying because the only way to trigger the AJAX call is through a single event. That's doable but it's limited. Here is the actual code I have in place, right now. This code is based largely on the current use of the 'use-ajax' class in ajax.js right now:

    // Attach a timer event.
    $('.aajax-example-use-timer:not(.ajax-processed)').addClass('ajax-processed').each(function () {
      var element_settings = {};

      // Clicked links look better with the throbber than the progress bar.
      element_settings.progress = { 'type': 'throbber' };

      // For anchor tags, these will go to the target of the anchor rather
      // than the usual location.
      if ($(this).attr('href')) {
        element_settings.url = $(this).attr('href');
        // Here is the magic. We'll trigger a custom event rather
        // than a click
        element_settings.event = 'AJAXExampleCustomEvent';
      }

      // ::::: Here we add a query parameter.
      element_settings.submit = { js: true, xyzzy: true };

      var base = $(this).attr('id');
      var ajax = new Drupal.ajax(base, this, element_settings);

      $(this).click(function() {
        if (!ajax.timer) {
          // We use a trigger because, as of right now, the function
          // that actually handles the AJAX initiation cannot be
          // called directly or changed because it is anonymous.
          // If http://drupal.org/node/939568 gets committed we could
          // instead do:
          // ajax.eventResponse($(ajax.element));

          // Trigger one right away so there's immediate response:
          $(ajax.element).trigger('AJAXExampleCustomEvent');

          // Set up the interval so that it will keep happening.
          ajax.timer = setInterval(function() {
            $(ajax.element).trigger('AJAXExampleCustomEvent');
          }, 3000);

          // Change the text of the link so that the user can
          // turn it off again.
          $(this).html(Drupal.t('Deactivate timer'));
        }
        else {
          // Remove the interval.
          clearInterval(ajax.timer);
          ajax.timer = 0;

          // Change the text of the link so the user can reactivate it.
          $(this).html(Drupal.t('Activate timer'));
        }

        return false;
      });

      Drupal.ajax[base] = ajax;
    });

The comment in there shows where calling ajax.eventResponse() would probably be more straightforward than having to go through an event. Plus, if there are multiple paths that could call this, a single event could become problematic, since right now the event cannot carry any payload. Plus, we can't actually change the behavior of the event response anyway.

rfay’s picture

You explained what the change means to ctools, but not what the change means to core. Since we need to get reviews that will get it into core, I'm trying to get you to go into detail about what it means to core.

I'm trying to be helpful, not annoying. We need some review, but this is presented as "ctools needs this". Which is OK. But it doesn't get it reviewed.

merlinofchaos’s picture

Actually I did, Randy, in my very first post:
By being named, this means that it can be replaced by a contrib module,

And then I followed up and wrote a book. What more do you want? This is way, way, way, way, way more work than a patch that doesn't do much should take. :/

rfay’s picture

@merlinofchaos, I apologize. Somehow I didn't see your #9, and my #10 was responding to #7.

Thanks for the explanation, and sorry for the cross-post (not that it was a cross-post; must be something the wrong with my ability to parse an issue)

merlinofchaos’s picture

Issue tags: +API change

This really needs love from someone to get in. I've done all I can to demonstrate what it is and why it's important.

Tagging per webchick's advice.

effulgentsia’s picture

effulgentsia’s picture

@merlinofchaos: Sorry. Since ksenzee is a much better JS programmer than I am, I was leaving it for her to RTBC. But she's on vacation. Patch no longer applies. Please post a new one, and I'll review.

Status: Needs review » Needs work

The last submitted patch, 939568-named-ajax-trigger-function.patch, failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new4.35 KB

Re-rolled against HEAD. Also added pass-through of the actual event to eventResponse().

Looks ready to fly for me.

merlinofchaos’s picture

StatusFileSize
new4.05 KB

Ok, reroll of the patch. I have:

1) Confirmed everything using node/add/poll and the ajax response example in my D7 presentation, which is available on my blog.

2) Confirmed that the eventResponse function is swappable by modifying the aajax response module with this:

      ajax.oldEventResponse = ajax.eventResponse;

      ajax.eventResponse = function(trigger) {
        alert('this dialog makes you wait for the trigger');
        return ajax.oldEventResponse(trigger);
      };
      Drupal.ajax[base] = ajax;

Ok testbot: Tell me this is ok!

merlinofchaos’s picture

I also confirmed that both clicks and keypresses trigger ajax appropriately.

merlinofchaos’s picture

aw crap. I didn't see your reroll, sun, and we crossposted.

merlinofchaos’s picture

StatusFileSize
new4.71 KB

Comparing sun's patch to mine, they both had bugs. sun's reverted the setClick bug that was fixed recently and mine didn't properly pass the actual triggering event through (which most things did not really use).

This merges the two patches.

merlinofchaos’s picture

StatusFileSize
new4.84 KB

Nope, missed one other item. Not sure how that disappeared.

sun’s picture

+++ misc/ajax.js	17 Nov 2010 18:53:49 -0000
@@ -201,15 +175,74 @@ Drupal.ajax = function (base, element, e
+Drupal.ajax.prototype.keypressResponse = function (trigger, event) {
...
+Drupal.ajax.prototype.eventResponse = function (trigger, event) {

Can we rename the first arguments to 'element'? All the code comments refer to an element already, and since we cannot use 'this', 'element' would make most sense.

Powered by Dreditor.

sun’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new4.35 KB

Merely changed the local 'trigger' variable to 'element' in the callbacks.

Aside from that, this patch is identical to #22, which in turn is almost identical to my last, except that this.element became ajax.element.

merlinofchaos’s picture

Looks good.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

This smelled very D8-y to me upon first read, but ksenzee and sun explained to me that this isn't a "feature", per se, but more of a code clean-up caused by the AJAX framework in core first being used right now. If JS code is written correctly, you basically get altering for free, so this is not a new "feature" in that respect like other patches I've been moving to D8 tonight.

Extra bonus is that there's no functionality change in core since it's basically just re-writing a function, and merlinofchaos is right that we've allowed similar patches more recently to clean up regressions from CTools.

So, committed #24 to HEAD. Thanks!

rfay’s picture

Congrats on getting this done! Wow.

I don't see an obvious API change that current users will trip over, so will not announce this unless somebody thinks otherwise.

Status: Fixed » Closed (fixed)
Issue tags: -API change

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