The 'use-ajax' class allows you to have a link that loads content via ajax when clicked. One possible use case for this is tabbed content, where clicking on a tab loads the tab's content via ajax. However, one would expect that once the content has been loaded the first time, it should just stay there, and in the case of tabs it would just be shown or hidden based on subsequent tab clicks. There is a handy new method in jQuery called .one() which works similarly to .bind() except that the attached behavior is removed after it has been invoked the first time. It would be nice to have the flexibility to tell the ajax system to use this instead of .bind() by just adding an extra class to your link, e.g.

<a href="mypath/nojs" class="use-ajax ajax-once">My Tab</a>

The attached patch achieves this flexibility.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

sun’s picture

Status: Needs review » Needs work
+++ misc/ajax.js	12 Apr 2010 17:51:26 -0000
@@ -44,6 +44,10 @@ Drupal.behaviors.AJAX = {
+      // should we use bind or one to attach the behavior?

@@ -144,9 +148,9 @@ Drupal.ajax = function (base, element, e
+  // function to bind to the element event

These should be proper sentences, starting with a uppercase letter and ending in a period.

+++ misc/ajax.js	12 Apr 2010 17:51:26 -0000
@@ -44,6 +44,10 @@ Drupal.behaviors.AJAX = {
+        element_settings.bindOnce = true;

@@ -165,7 +169,14 @@ Drupal.ajax = function (base, element, e
+  if (element_settings.bindOnce) {
+    $(this.element).one(element_settings.event, func);
+  }
+  else {
+    $(this.element).bind(element_settings.event, func);

Hm. Isn't there some nifty dynamic method callback construct we could use here? I.e. define "bind" as default bind method, override it via the CSS class, and call something along the lines of:

$(this.element).call(element_settings.bindMethod, function () {
  ...
});
+++ misc/ajax.js	12 Apr 2010 17:51:26 -0000
@@ -144,9 +148,9 @@ Drupal.ajax = function (base, element, e
+  ¶

Trailing white-space here.

107 critical left. Go review some!

katbailey’s picture

Heh, that's exactly the way I tried it at first! The problem is that element_settings.bindMethod is a string not a function, so there's no way to do that without using eval somewhere along the way. Unless you mean there's some way I can create a pointer to the jQuery bind() method..? Anyway, I'll make those other changes...

rfay’s picture

subscribing

katbailey’s picture

FileSize
1.83 KB

Made those punctuation changes, will see if I can come up with anything better for that other issue...

rfay’s picture

@katbailey, could you please post a boiled-down module that demonstrates this use-case and allows easy testing of the patch? Since we don't have testing for javascript, we need easy ways to experiment with things like this.

rfay’s picture

Status: Needs work » Needs review
katbailey’s picture

FileSize
13.24 KB

OK, for now I'm just attaching a version of ajax_example module where I've changed the ajax link example to render two links, one that uses the ajax-once class and one that doesn't. So, there are no actual tabs here. However, I almost have jQuery UI Tabs and the Ajax framework working in perfect harmony together to create the ultimate D7 tabs solution (Quicktabs ;-)), and it needs this change! (See here for more info on that: #553070: Merge with tabs module for D7?)

katbailey’s picture

Here's a demo of my use case: jQuery UI Tabs + Ajax framework = awesome :-) http://174.143.25.221/
The point is that once we've used the ajax framework to load the tab content via ajax, jQuery UI tabs can take over and just show/hide as needed.
It should be noted that using this "ajax-once" option will require another click event handler that at least just does return false; so that the browser doesn't follow the link now that the original event handler has been detached. In the case of the tabs example, jQuery UI Tabs' click handler does this for us. However in the ajax_example module above, I needed to add an extra js file to provide this basic click event handler.

RobLoach’s picture

Awesomeness! We need more jQuery.one in Drupal.

katbailey’s picture

I've been thinking some more about the possibility of calling functions whose name is stored as a string in a variable, as per Sun's comment above. Another use of that would be the ability to specify a js callback function in your ajax commands so you could run your own js after ajax.js has worked its magic, which would also increase the flexibility of this mechanism. I remembered that ajaxsubmit module does such a thing and had a look at the code that invokes callback functions - here it is:

  // Invoke any callback functions.
  if (data.__callbacks) {
    $.each(data.__callbacks, function(i, callback) {
      eval(callback)(target, data);
    });
  }

Looks like eval is the only way.
So, for the purposes of the problem I mention above, I think the patch as it stands is the best way to go - no need for eval here. As to whether we want to add the custom callback option, I'll leave that for another issue. I will try to get more eyes on this one - I guess the main problem is lack of use cases as not many people are using this mechanism yet.

effulgentsia’s picture

subscribe

katbailey’s picture

FileSize
2.11 KB

This is just a slight change to the patch, separating out the 'return false' behaviour into its own click handler that always gets bound using .bind() so that it's only the actual ajax behaviour that would be bound using .one(). This way it's not necessary for a module that uses this mechanism to attach its own click handler in order to prevent the browser following an ajax link that's been bound using .one().

Sid_M’s picture

I apologize for not rolling a patch, but I'm not set up for that on this machine. Nonetheless, here's how to implement a dynamic callback function as discussed in #1 and #2. As Sun suggests, create element_settings.bindMethod which can equal 'bind', 'one' or any other jquery method name as appropriate. Then use js's syntax for tokenizing a string, which is to put it in brackets. So these two method calls:

  $(this.element).one(element_settings.event, func);
  $(this.element).bind(element_settings.event, func);

get replaced by this one

  $(this.element)[element_settings.bindMethod](element_settings.event, func);

Note that the same approach can be used to eliminate the eval statement in #10.

In case the above isn't clear, here's a bit more. Anywhere there's a dot, it can be replaced with brackets, and the brackets can contain any expression (including function calls) which evaluates to a string which is the name of a property or method contained by the object which precedes the opening bracket.

In cases where a variable or function is global, precede the brackets with window. For example:

var foo = 'bar';
var foo1 = window[foo];
katbailey’s picture

FileSize
3.08 KB

Sweet! I remember seeing js code like this before and not being able to make head or tail of it - now it all makes sense! Thanks, Sid_M :-)
Updated patch attached.

sun’s picture

Status: Needs review » Needs work
+++ misc/ajax.js	21 Apr 2010 18:07:24 -0000
@@ -38,12 +40,18 @@ Drupal.behaviors.AJAX = {
     $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () {
...
+      // Check whether the behavior should be bound using .one() instead of .bind().
+      if ($(this).hasClass('ajax-once')) {
+        element_settings.bindMethod = 'one';
+      }

Shouldn't we support .ajax-once for other integration methods than .use-ajax, too?

+++ misc/ajax.js	21 Apr 2010 18:07:24 -0000
@@ -145,8 +156,14 @@ Drupal.ajax = function (base, element, e
-  // Bind the ajaxSubmit function to the element event.
-  $(this.element).bind(element_settings.event, function () {
+  // We must persistently bind the 'return false' behavior to the link
+  // regardless of whether the ajax behavior should persist after one click.
+  $(this.element).bind(element_settings.event, function(){
+    return false;  ¶
+  });
+
+  // This is the ajax event handler that will be bound to the element event.
+  var func = function () {

@@ -163,9 +180,9 @@ Drupal.ajax = function (base, element, e
     }
-
-    return false;
-  });
+  };
+  // Bind the handler to the element event using whichever bind method was set.
+  $(this.element)[element_settings.bindMethod](element_settings.event, func);

1) We do not have to split the function into 'func'; just replace the original line containing bind().

2) To make any sense of the "return false", the second event handler has to be bound after the AJAX event handler, not before. I'd additionally suggest to only conditionally add it, if bindMethod != 'bind'.

3) Anonymous functions should have a space after "function", and there should also be a space before the opening curly brace {.

4) Trailing white-space.

Powered by Dreditor.

katbailey’s picture

Thanks for the feedback, Sun. I had wondered about your first point, i.e. whether to allow this option for elements using #ajax, when I was doing the last iteration of the patch. I can certainly go ahead and add that in. As to the other points:
1) Yes, this was a holdover from when I had to do the if/else statement and had two versions of the function call - will change it back to an anonymous function.
2) I'll change the order. I had actually moved 'return false' out of the other click-handler so then you would need it regardless of .one or .bind. You think it should be in the ajax click handler anyway and just bound again separately if bindMethod != 'bind'? I guess that might be slightly better.
3) Yessir
4) Yessir
;-)

katbailey’s picture

Status: Needs work » Needs review
FileSize
2.27 KB

Re-rolled as per #16.

sun’s picture

+++ misc/ajax.js	26 Apr 2010 04:56:36 -0000
@@ -44,6 +44,10 @@ Drupal.behaviors.AJAX = {
+      // Check whether the behavior should be bound using .one() instead of .bind().

Exceeds 80 chars. (can be shortened)

+++ misc/ajax.js	26 Apr 2010 04:56:36 -0000
@@ -145,8 +149,13 @@ Drupal.ajax = function (base, element, e
+  if (!element_settings.bindMethod) {
+    // The default method for binding behaviors is .bind().

@@ -163,10 +172,16 @@ Drupal.ajax = function (base, element, e
+  if (element_settings.bindMethod == 'one') {
+    // We must persistently bind the 'return false' behavior to the link.

Can we move the inline comment above the if condition? It's easier to read the code flow that way.

Additionally, the second comment just repeats what can be easily read from the following code lines. It should explain the usual "why?" and "WTF?" questions instead. ;)

96 critical left. Go review some!

katbailey’s picture

Better?

sun’s picture

+++ misc/ajax.js	26 Apr 2010 22:02:47 -0000
@@ -145,8 +149,13 @@ Drupal.ajax = function (base, element, e
+  // The default method for binding behaviors is .bind().
+  if (!element_settings.bindMethod) {

"By default, behaviors are bound with .bind()."

+++ misc/ajax.js	26 Apr 2010 22:02:47 -0000
@@ -163,10 +172,19 @@ Drupal.ajax = function (base, element, e
 
+  if (element_settings.bindMethod == 'one') {
+    // If we don't persistently bind the 'return false' behavior, then elements
+    // whose AJAX behavior was bound using .one() will revert to their default
+    // behavior. For example, clicking on a link that had an ajax behavior will,
+    // after the first click, result in the browser following that link.

Can we move the comment above the condition?

"If the behavior was only bound once, the browser's default behavior needs to be prevented on subsequent events."

("prevented" is not JS jargon in this context, but I just have a blackout, so there's probably a better term.)

Powered by Dreditor.

sun’s picture

Title: It should be possible to have ajax behaviors bound using .one() instead of .bind() when using 'use-ajax' links » AJAX behaviors cannot be bound once on links
Category: task » bug

Better title.

sun’s picture

Looks ready to fly for me.

effulgentsia’s picture

+++ misc/ajax.js	29 Apr 2010 18:04:51 -0000
@@ -27,6 +27,11 @@ Drupal.behaviors.AJAX = {
+
+          // Check whether to bind behaviors using .one() instead of .bind().
+          if ($(this).hasClass('ajax-once')) {
+            element_settings.bindMethod = 'one';
+          }

Why do we need to check for the class here? This is the code block that has #ajax bindings. Shouldn't the PHP code be responsible for adding bindMethod to it?

+++ misc/ajax.js	29 Apr 2010 18:04:51 -0000
@@ -163,10 +177,17 @@ Drupal.ajax = function (base, element, e
-
     return false;
   });
 
+  // If the behavior was only bound once, the browser's default behavior needs
+  // to be prevented on subsequent events, so as to not follow links.
+  if (element_settings.bindMethod == 'one') {
+    $(this.element).bind(element_settings.event, function () {
+      return false;
+    });
+  }
+

Any reason not to remove the first "return false" and always bind the function that just returns false?

Powered by Dreditor.

effulgentsia’s picture

Also, for the code that must check for class (because links don't have #ajax bindings), what about using a class of ajax-bind-method-METHOD (i.e., ajax-bind-method-one)?

[EDIT: or even crazier, ajax-setting-SETTING-VALUE, so something like ajax-setting-bindMethod-one. Seems like there might be other use-cases for needing settings on links.]

[EDIT: sorry, ignore this comment. Advanced use-cases can use #attached to add settings. Here, we want something easy for a common use-case. Given that, "ajax-once" makes sense.]

effulgentsia’s picture

Maybe it's just bike shedding, but how about this?

sun’s picture

omg - let's not make this even more complicated than it is already, please! :)

(also, I actually hoped for a RTBC here finally as this is just one of the issues that fix ajax with links... ;)

effulgentsia’s picture

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

Discussed with sun. The differences between #22 and #25 only start mattering if and when other AJAX patches land. #22 solves this issue completely adequately given current HEAD. Tweaks to the logic can be rolled into the other patches. So, re-uploading #22 and RTBC'ing it.

aspilicious’s picture

#27: drupal.ajax-bind-once.22.patch queued for re-testing.

aspilicious’s picture

*Retesting old patches*

katbailey’s picture

bump - not sure why this is still stagnating here :-(

sun’s picture

#27: drupal.ajax-bind-once.22.patch queued for re-testing.

sun’s picture

#27: drupal.ajax-bind-once.22.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, drupal.ajax-bind-once.22.patch, failed testing.

pfrenssen’s picture

I tried rerolling this but the patch can not be applied anymore since #939568: Enable finer control for ajax-using modules of the trigger response in the ajax object got in.

katbailey’s picture

Here's a re-roll.
Though I've just been looking through the issue mentioned in #34 which I was oblivious to at the time and realising that if that patch had just gone a little further, then we wouldn't need this one so much. That patch enabled other modules' js to overwrite the event response because it is now a named function. But it's being called by an anonymous function which is actually what gets bound to the element. If we bind a named function as the event handler then we can unbind it, i.e.

$('#myElement').unbind('click', eventResponse);

Thoughts?

rfay’s picture

Version: 7.x-dev » 8.x-dev
Status: Needs work » Needs review
Issue tags: +Needs backport to D7
FileSize
2.3 KB

Has to go into D8 first. Shucks. Attaching #35's patch to get it tested on D8.

Status: Needs review » Needs work

The last submitted patch, 769258.ajax-bind-once.35.patch, failed testing.

katbailey’s picture

OK, I'll try and do a reroll this evening...

katbailey’s picture

FileSize
2.36 KB

Let's see if this one applies...

blup’s picture

+1

RobLoach’s picture

Status: Needs work » Needs review

Herro, patch bot?

katbailey’s picture

Thanks Rob! Was wondering why it was being ignored... duh

driki_’s picture

Just tested this patch on drupal 7 for some use case we have. Looks to work fine to me.

driki_’s picture

Version: 8.x-dev » 7.x-dev
Category: bug » feature

edit : changed core version, category.

aspilicious’s picture

Version: 7.x-dev » 8.x-dev
Category: feature » bug

This is a bug and we need to fix bugs first in D8. When it's committed to D8 it will be committed soon to D7.

driki_’s picture

this is the patch from comment #30 with the path corrected ( core/misc )

aspilicious’s picture

Status: Needs review » Needs work
+++ b/core/misc/ajax.jsundefined
@@ -173,11 +182,25 @@ Drupal.ajax = function (base, element, element_settings) {
+ ¶

trailing whitespace, in 2 places and there is an unneeded newline.

0 days to next Drupal core point release.

driki_’s picture

Status: Needs work » Needs review
FileSize
2.03 KB

removed newline and whitespace

nod_’s picture

Status: Needs review » Needs work

This need to use e.preventDefault() instead of return false.

Also I'm not really getting the use of .one if you need to do a .bind on it later on. Can't you just do a .bind and have your if inside that?

Better yet have a .bind canceling events for every element and add either a .one or a .bind depending on the class? You can have several listeners for one event.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

heddn’s picture

Issue summary: View changes
Status: Needs work » Postponed (maintainer needs more info)

I think this needs some significant attention. Marking as postponed since we need to confirm this is still an issue as a lot of time has passed in the past 5 years.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev
quietone’s picture

@katbailey, Thank you for reporting this problem. We rely on issue reports like this one to resolve bugs and improve Drupal core.

Is this issue still a problem?

There has been no activity on the patch for 10 years and more information was asked for 5 years ago.

Since we need more information to move forward with this issue, I am keeping the status at Postponed (maintainer needs more info). If we don't receive additional information to help with the issue, it may be closed after three months.

Thanks

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Kristen Pol’s picture

Thanks for reporting this issue. We rely on issue reports like this one to resolve bugs and improve Drupal core.

As part of the Bug Smash Initiative, we are triaging issues that are marked "Postponed (maintainer needs more info)". This issue was marked "Postponed (maintainer needs more info)" more seven year ago and there has been no activity here since that time other than @quietone asking for more information.

Per #61, I'm marking the issue "Closed (cannot reproduce)". If anyone can provide complete steps to reproduce the issue (starting from "Install Drupal core"), document those steps in the issue summary and set the issue status back to "Active" [or "Needs Work" if it has a patch, etc.].

Thanks!

Kristen Pol’s picture

Version: 9.4.x-dev » 7.x-dev

Whoops. Forgot to close but... @klonos noticed that this could be moved to D7 queue.