Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Further to an irc discussion with sun, where I mentioned my original misunderstanding of how the use-ajax link class should work, i.e. to make a rendered link that can have an '#ajax' property, here's a re-roll of the patch I posted in that original issue #610068: Document AJAX no-js and use-ajax. It sounds like he has some bigger plans for ajax_process_form though anyway, as per #645822: #ajax is not extensible (and partially buggy).
Comment | File | Size | Author |
---|---|---|---|
#81 | drupal.ajax-link-disabled.81.patch | 1.03 KB | sun |
#75 | drupal.ajax-link.75.patch | 13.09 KB | sun |
#74 | drupal.ajax-link.73.patch | 13.09 KB | sun |
#72 | drupal.ajax-link.71.patch | 11.96 KB | sun |
#70 | drupal.ajax-link.70.patch | 9.67 KB | sun |
Comments
Comment #1
sunThanks!
So this mainly means:
1) add #type = 'link' to the existing switch in ajax_process_form(), assigning 'event' => 'click' for anchors.
2) make ajax_process_form() form-$element-aware, i.e. before trying to use/access/assign stuff like $element['#name'], check whether we're in a form context.
3) invoke (a renamed) ajax_process_form() from within drupal_render(), just like we do for drupal_process_states() and drupal_process_attached().
Comment #2
casey CreditAttribution: casey commentedAny progression here? we don't have much time anymore.
Comment #3
fagoI've opened a related issue for using #ajax on a link from inside a form: #753270: Impossible to trigger form submit with a link
With adding "ajax_process_form" it's working pretty well - except for the little js fix needed (see patch over there). However #1 sounds like a good way to go for this one.
Comment #4
katbailey CreditAttribution: katbailey commentedOk, I'll try and have this re-rolled as per #1 by tomorrow.
Comment #5
RobLoachI'm trying to get this working on the "More information on filter tips" link on the node add form, but am having absolutely zero success.
Comment #6
katbailey CreditAttribution: katbailey commentedOK well here's what I've got so far. You can test it with the attached version of the ajax_example module (the ajax link example) but I think we need more than this, just wanted to get this much up as a start...
Comment #7
sunCleaned this up a bit.
Comment #8
sunMore. drupal_pre_render_link() didn't take #attributes into account. Only #options[attributes], WTF?
Comment #9
sunFixed missing url() for $element['#href'] in ajax_process_element(). #href is an internal system path.
Works pretty fine for me already. What's left?
Comment #10
sunShould default to array()
meh, should also take into account #options['attributes']['id']...
Needs to be moved outside of this condition and set if the passed $form_state is !empty()
Consider to set effective attributes to $elements['#attributes']
93 critical left. Go review some!
Comment #11
katbailey CreditAttribution: katbailey commentedThe way it works now, you'd have to have a #ajax array in your link element just so that it would be ajax processed although there wouldn't necessarily be anything you'd need to put in it, seeing as 'event' is already taken care of (defaults to click), and putting your 'wrapper' in there won't actually achieve anything. However it would make sense if you could put your wrapper in there and have that passed to your callback - this seems more sensible than having the menu callback decide where to append the content. So, something along the lines of (line 110 of ajax.js):
So then your callback function would take two paramenters, e.g.
Thoughts?
Edit: I have a sneaking suspicion I was being stupid with this comment, and that the menu callback should just be returning a renderable array and the js will use the wrapper specified in the #ajax array to append it to. Need to do more testing to see if that's how it works. It certainly makes no sense whatsoever to specify a wrapper in the php, pass it to the js, which passes it back to the php, which eventually passes it back to js again :-P
Comment #12
katbailey CreditAttribution: katbailey commentedHere's a re-rolled patch that takes in sun's points in #10 (assuming I understood the last one correctly).
We still need #645800: ajax_deliver() ignores #ajax['method'] and incorrectly forces 'replaceWith' for simple AJAX callbacks, D6->D7 regression in order to have this fully functional so I'll see if I can get that done tonight.
Comment #13
RobLoachDid a couple things to it:
Would progress effects work on this bad boy?
Comment #14
RobLoachShould we move this into
drupal_pre_render_link()
? I guess not, because all links don't need IDs. But, you should still be able to use #id on link types if it's not using #ajax.Powered by Dreditor.
Comment #16
RobLoachThe test actually passed.
Comment #17
sunThis comment can be removed -- it does not provide any useful information that wouldn't be obvious from the following code :)
We can shorten the comment to state that #ajax][path has precedence over #href.
Additionally, I'd prefer a proper if/else condition here, whereby we only unset() in the case of #ajax][path.
Same as mentioned below: #options['attributes'] should have precedence. This gets a bit hairy, but I also don't see another way. Priorities I imagine:
1) Take #options][attributes][id if set, and copy it over to #attributes][id and #id.
2) Take #attributes][id if set, and copy it over to #options][attributes][id and #id.
3) Take #id if set, and copy it over to #options][attributes][id and #attributes][id.
4) Generate a new "#ajax-link" id, and set it to all three properties.
Trailing white-space.
#options should have precedence over #attributes, because #options['attributes'] is the "official" attribute that is supported and gets passed. We only need to support #attributes additionally, because its the default location for AJAX and form API.
What I meant above is that it would perhaps be worth to set the resulting/effective #options back into $elements, so any subsequent #pre_render/#theme/etc. can access it -- i.e. instead of populating an anonymous/private $options variable here, we populate $elements['#options'].
96 critical left. Go review some!
Comment #18
katbailey CreditAttribution: katbailey commentedHere we go... however I noticed that in ajax_process_element where we force an id on the link, we're allowing for the possibility that $element['#id'] might not have been set, but this would cause an error earlier in the process when we are creating the settings array (line 461 of ajax.inc):
So, do we just assume that $element['#id'] has in fact been set and not include the other possibility when we're doing the 'Force an ID on the link' bit? Or do we need to re-jig this code to do the check further up?
Comment #19
sunYes, we need to move this code back up to where I put it originally, so $element['#id'] is properly set. The tests only passed, because there is no test for #type link using #ajax yet.
Coding standard is "elseif" (without space).
Stale blank line here.
96 critical left. Go review some!
Comment #20
RobLoachIt won't work beside
$element['#ajax']['event'] = 'click';
because that is just setting the default event if "event" is not set. If you change the event to "mouseover", for example, the force ID code would not run and the element would not have an ID.The other two points are valid though.
Comment #21
sunAh, sure, you're right. Wasn't visible from the patch context.
So this one should be ready to fly.
Comment #23
katbailey CreditAttribution: katbailey commentedThe crazy id assignment stuff breaks one of the form tests; making sure we only do that for #type 'link' (which after all is how it's explained in the comment) stops said test from failing.
Comment #24
katbailey CreditAttribution: katbailey commentedComment #25
sunSomething is wrong with the testbot, but this patch looks very good to me. Not particularly happy about the #type 'link' special-casing, but we can make this more generic when a requirement to do so comes up in the future; can't think of other elements currently.
Thanks!
Comment #26
moshe weitzman CreditAttribution: moshe weitzman commented#23: drupal.ajax-link.23.patch queued for re-testing.
Comment #27
Dries CreditAttribution: Dries commentedWhy should this be considered for Drupal 7?
Comment #28
katbailey CreditAttribution: katbailey commented@Dries, to me at least, it seems like a huge oversight if this doesn't get in: when the AJAX framework came into core from CTools, pretty much all further development on it focused on AJAX forms as this was by far the most challenging aspect of it. The ability to load content via ajax by clicking on a link (completely unrelated to forms) was there, but undocumented and unused. CTools for D6 has continued to develop both aspects of its ajax framework (form-related and otherwise), for example providing a solution to the problem of lazy-loading css and js for ajax-loaded content. Meanwhile in core, such development (e.g. #561858: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework) focuses only on forms.
In D6 a lot of confusion arose from the fact that the term "AHAH" referred to a very specific type of functionality, namely dynamically loading form elements. We're in danger of doing the same thing all over again in D7, where the AJAX framework is actually just an AJAX forms framework, and the vast multitudes of developers who are trying to do a very basic thing like loading a node or a View via ajax will be confounded to eventually realise that it doesn't provide them much help at all. Of course, if #561858: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework doesn't make it in, then this becomes less relevant.
That's my perspective on it anyway. It would be good to get merlinofchaos's view.
Comment #29
merlinofchaos CreditAttribution: merlinofchaos commentedI agree with Kat in the sense that core's focus on AJAX for forms is not where, IMO, the real power of AJAX lies. ahah.js was developed to allow us to do cute tricks like adding more options to the poll options dynamically, or being able to deal better with book navigation.
ajax.js was designed to create powerful, robust AJAX interfaces where forms are subservient to the system, not the other way around. With the fapi renderer being genericized and used more across Drupal, it is good to keep that goal of the system in mind. Making it convenient to add AJAX functionality to links wherever they are rendered will allow authors to add robust AJAX functionality easily. Right now, the framework still has a few limitations that are going to prevent that. This is a nice API improvement that won't break pre-existing APIs at all, so it's a good way to keep THIS side of the ajax framework up with the work that's been done on forms.
IMO it's not as important as #561858: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework which I think will be necessary to allow AJAX interfaces to truly shine.
Comment #30
sunThis needs to be considered for D7, because this issue was created after a contributed module tried and failed to use the new AJAX framework in D7. Actually, it's a bug report.
Comment #31
fagoGenerally the #ajax system is currently not usable with #link, neither with or without forms. I agree, that this is a bug that needs to be fixed - but to work in both cases (also see #753270: Impossible to trigger form submit with a link)
Why is the element skipped in case $element['#href'] is set? Furthermore $element['#href'] is used afterwards in the function, which together makes no sense at all.
* Also renaming ajax_process_form() to ajax_process_element() is an API change, as any form element provided by contrib making use of the #ajax system has to adapt. Maybe we could keep the old function name, which makes use of the new ajax_process_element() but additionally covers FAPI stuff, thus enables caching?
* I like the #id syncing part - having different ways to specify the id is awkward. Till now I had to define the same id using two different ways at the same time to get it working for my use case, so +1 to fix that.
However drupal_pre_render_link() doesn't take over the id specified via #id, but the #ajax handling of links does. Thus if there is no #ajax, #id would stop working. That's inconsistent, #id should be taken into account regardless whether #ajax stuff is there.
* With the patch ajax processing of a link is moved to pre_render - but what if the link is used in a form? Then processing won't turn on form caching, but it would basically work - weird. Imo we should also add the usual ajax_process_form() as #process handler and do nothing in ajax_process_element() in case the element has been #processed - therefore assuming ajax_process_form() has been invoked then.
Comment #32
sun1) It's the opposite, as the entire condition is negated. One of the three properties must be set to continue.
2) I wouldn't be concerned about changing the name of this #process function. I didn't grep D7 contrib, but I'd be surprised if any module would actually specify it. Since it's no longer a FAPI-only function, but a RAPI preprocessor instead, it would be good to name the function accordingly.
3) is impossible to do without introducing yet another callback function just for the #id. Bad for performance. If you want an ID, then simply assign 'attributes']['id', which works for everything.
4) We'd have to specify both a #process and a #pre_render, and somehow identify whether the element was already processed via #process. I'm not sure whether it makes sense to use a link within a form to invoke actual form processing in the first place though -- how do you invoke the same action without JavaScript being enabled? So, from a logical standpoint, I'd say that this point is moot...?
Comment #33
fago1) arg, I see.
2) Well, I did. I'm surely not the only one. My suggestion was to build the FAPI function around the RAPI preprocessor - so we would have an appropriate function name for the RAPI preprocessor + BC.
3) What's the problem with taking #id into account in drupal_pre_render_link()?
4) FAPI sets
$element['#processed'] = TRUE;
- it should be as simple as checking that. For degrading from the link in forms I simply use the trigger_as functionality of #ajax, which makes it easy for a link to degrade to a button (hide the button by JS, show the link by JS). I use it to switch parts of the form, having buttons for that visually clutters the form too much.Comment #34
effulgentsia CreditAttribution: effulgentsia commentedSubscribe. Will hopefully find some time to read this and have something useful to say about it, but please don't hold it up on my account.
Comment #35
rfaysubscribe
Comment #36
katbailey CreditAttribution: katbailey commentedHi All,
I hope it's not too late to breathe new life into this and get it in, it seems we're so close. I made the change that fago was looking for, i.e. splitting it out into the FAPI function ajax_process_form, which in turn calls ajax_process_element. I have not tested it with his particular use case, i.e. having a link that submits a form. I've been testing this using ajax_example module and am attaching a patch to that that modifies the ajax link example to use a rendered array with the #ajax property for the link. I hope some of you have a chance to test this out soon.
Comment #37
katbailey CreditAttribution: katbailey commentedForgot to change the status.
Comment #39
sunI thought this would've been committed years ago? Unfortunately, quite some failures for the last patch.
Comment #40
mfer CreditAttribution: mfer commentedsubscribe
Comment #41
katbailey CreditAttribution: katbailey commentedWow, 90 fails! And unrelated to ajax stuff it looks like at first glance. Weird - I'll try and get to it today and see what's going on there.
Comment #42
rfay#36: drupal.ajax-link.36.patch queued for re-testing.
Comment #43
katbailey CreditAttribution: katbailey commentedOops, stupid mistake in the last patch (yay for tests!), this one should be good...
Comment #44
sunFixed a couple of comments, and renamed ajax_process_element() into ajax_pre_render_element(), as it's a #pre_render callback now.
Additionally, I've introduced #ajax_processed as indicator for successful AJAX processing (i.e., something was done). And used that to replace the form caching condition in ajax_process_form(). I think we also have to enable form caching for links, to be on the safe side. We don't know yet what kind of weird things people are going to build with all of this stuff.
Comment #45
sunI've manually tested this patch again today. Still works like a charm. Looks RTBC to me. Any other feedback?
Comment #46
sunRe-rolled against HEAD. Hey, feedback, anyone?
Comment #47
a25i CreditAttribution: a25i commented#46: drupal.ajax-link.46.patch queued for re-testing.
Comment #48
sunSo I've researched and analyzed jquery.form.js' code a bit and it turns out that we can totally easily abstract the required AJAX POST value handling in ajax.js.
Attached patch additionally contains the follow-up patch from #850612: setting class 'use-ajax-submit' for form submission causes js error(s) - sorry for that.
This patch effectively makes http://drupalcode.org/viewvc/drupal/contributions/sandbox/sun/core_tabs/ work again! :)
Comment #49
sunSorry, Kat, taking this over for now (unless you beat me to it) ;)
Comment #50
rfayOK with me as long as the code gets demonstrated in action and tested in real life (and your core_tabs is a good thing :-)
Comment #51
sunThat apparently looks like nonsense. Seems like we can skip the entire for...in loop and just use the global Drupal.settings variables ;)
Powered by Dreditor.
Comment #52
sunSo here comes a heavily simplified patch. A minor change to the new lazy-loading code allows us to pass any ajax page state data to the server, which is extremely nice, as that makes it modular.
This patch is RTBC based on my testing.
Comment #53
sunTo my knowledge, this is the last blocker for modules that are trying to use the AJAX framework. And since this is actually blocking a couple of modules from being ported to D7, bumping to major.
Comment #54
sunLatest patch breaks lazy-loading. options.data doesn't support objects, so um, yeah, I'll figure out something.
Comment #55
sunAttached patch fixes the primary bug. Leaving us with fixing broken merge of ajaxPageState in subsequent AJAX requests.
Comment #56
merlinofchaos CreditAttribution: merlinofchaos commentedThere is a major problem with this:
When using $.ajaxSubmit, the page state is being sent as:
Rather than the contents of the object. Maybe there needs to be a serialize process that isn't happening?
Comment #57
sunVery good finding, merlin! Attached patch fixes that, too. :) So this one should make everything work correctly.
EDIT: Actually, I meant your reply on the other issue ;)
Comment #58
sunSo this is the actual change that prevents Drupal.settings.ajaxPageState from being overwritten in subsequent AJAX requests.
We likely want to rename the function argument from $skip_alter to $ajax or similar, as the current state of affairs is obviously non-obvious. ;)
Powered by Dreditor.
Comment #59
merlinofchaos CreditAttribution: merlinofchaos commentedIt seems better to just unset the settings prior to calling drupal_get_js() in ajax_render(), rather than using a special parameter to drupal_get_js(). At least, to me.
// Settings are transmitted separately so they can be merged, so remove them// from the general javascript transmitted here:
unset($items['js']['settings']);
$scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
$scripts_header = drupal_get_js('header', $items['js'], TRUE);
?>
Comment #60
effulgentsia CreditAttribution: effulgentsia commentedI agree with #59. In fact, we had that in #561858-132: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework, but somehow lost it in subsequent patches.
Comment #61
sunOK, that's true and much better.
Comment #62
sunI'm still testing with rfay's excellent lazy_load_demo module from http://drupal.org/node/561858#comment-3352146 as well as my own core_tabs demo module (linked above) and everything seems to work perfectly with this patch.
One remaining bug that's not to be solved in this issue is that subsequent AJAX requests needlessly lead to new HTML IDs on content that is replaced. I already mentioned in #926016-21: Several bugs when trying to remove files from a multivalued file/image field that our current handling of #ajax][wrapper is not sufficient, as the wrapper HTML ID will change. This either means that we need a server-side facility to pass the right wrapper element #id to the client, or some other way. Perhaps, the aforementioned issue can be slightly re-purposed to tackle this not only for File module. You can reproduce the bug with rfay's lazy_load_demo module by toggling the "Enable #states behavior" button at least 3 or more times and then click the #states-driven checkbox -- it fails to apply, because the HTML IDs differ.
Comment #63
sunRe-rolled against HEAD. Lazy-loading has been fixed in the original issue. This patch still fixes lazy-loading for non-form AJAX and lastly, makes #type link compatible with #ajax.
Comment #64
sunAny feedback? This patch looks RTBC to me.
Comment #65
effulgentsia CreditAttribution: effulgentsia commentedI will look at this thoroughly later today. I'd love to know what others who participated in this issue think of #63 as well.
Comment #66
effulgentsia CreditAttribution: effulgentsia commentedVery nice. Overall, I really like the patch. Seems to exactly solve what is needed, and is backwards compatible, so no reason for it not to go into D7. Nits:
See ajax_pre_render_element()?
Why #processed? Shouldn't it be #ajax_processed? Suppose the link element has a #process function that isn't ajax_process_form().
How about this instead?
Not necessary, just a suggestion. I fear the code as-is will be a red flag to Dries/webchick.
Minor, but we use 'process' an awful lot, so it gets confusing. How about '#ajax_attached'?
Should this be changed to
ajax.beforeSerialize(ajax.element, ajax.options);
?Also, we need an example added to the docs at the top of ajax.inc, right?
And a test would be nice: at least one that checks Drupal.settings has what's expected for a link inside a form and a link outside a form.
Finally, I haven't tested this. @merlinofchaos, @katbailey: after above feedback is included, can you confirm it works for your use-cases?
Comment #67
sunRe-rolled with the easy changes.
- The insanity of #options, #attributes, and #id is something I don't think we can get around... we simply happen to support all of those elsewhere, in different sub-systems, so IMHO it would be illogical if #ajax would only account for one of all possible ways. The current code checks each possible property separately and takes over its value for the other possible properties accordingly. Kind of a mess, I agree, but that's what we have to deal with until D8+.
- Yes, we definitely need tests.
Comment #68
effulgentsia CreditAttribution: effulgentsia commentedFollowing up on #561858-208: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework, making this issue critical. Summed up well in #27-#29. Basically, there are two ways currently in HEAD, in which links are given 2nd class status with respect to AJAX:
1) They can't have #ajax settings, and therefore, can't specify things like 'wrapper', 'path', 'progress', etc., the way that form elements can. All they can have in HEAD is a "use-ajax" class, which uses defaults for all settings only. There's no good reason for this limitation. It came about historically, because when the D7 AJAX framework first went in, the generalization of parts of FAPI into a render API were still in progress, and we didn't have a #type => 'link' element type. Now we do, so it makes a lot of sense to make it a first class citizen, especially since there's no BC break in doing so.
2) They don't benefit from the lazy loading added in the other issue, because for no good reason, we put the magic JS code into beforeSubmit(), which only runs for form submissions, instead of into beforeSerialize(), which runs for both form submissions and link submissions. #67 fixes that.
I gave examples in #561858-207: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework, in addition to what's been written in #28 and #29, for why there's lots of good reasons for links to have full participation in the AJAX framework.
Discussing #67 with sun now, and one of us will follow up with another patch.
Comment #69
sunDiscussing extensibility and flexibility of ajax_pre_render_element() a bit more with eff and merlin, we came to the attached conclusion and simplification.
I'll try to simplify those giant if/else conditions in drupal_pre_render_link()... somehow.
Comment #70
sunHeavily simplified drupal_pre_render_link().
Comment #72
sunAdded tests. Really wanted to do more to partially cover the lazy-loading, but that requires API changes, so leaving that to a separate issue.
Comment #74
sun#type 'link' in a form automatically gets a HTML ID like any other form element now, which is naturally expected, and which explains the two failing assertions. Thanks eff for helping to figure this out! :)
Comment #75
sunoopsie :)
Comment #77
effulgentsia CreditAttribution: effulgentsia commented#75: drupal.ajax-link.75.patch queued for re-testing.
Comment #78
effulgentsia CreditAttribution: effulgentsia commentedCode looks great. Summary in #68. Simpletest added for #68.1. Simpletest not possible yet for #68.2, but as per #561858-201: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework, I will work on enhancing drupalPostAJAX() to be able to pseudo-test that at some point. In the meantime, the example module in #561858-207: [Tests added] Fix drupal_add_js() and drupal_add_css() to work for AJAX requests too by adding lazy-load to AJAX framework can be used for manually testing that, and shows that #75 works perfectly.
Hard-coded special-casing for 'link' within ajax_pre_render_element() that were part of earlier patches has been removed, achieving merlinofchaos's request that this be extensible to custom types (e.g., 'ctools_link').
Therefore, RTBC.
Comment #79
Dries CreditAttribution: Dries commentedCommitted to CVS HEAD. Thanks!
Comment #80
kika CreditAttribution: kika commentedWhat about docs / upgrade guide? Is it needed?
Comment #81
sunQuick follow-up fix: At least in Firefox, after clicking an ajaxified link, a
disabled="false"
HTML attribute remains on the anchor element, which prevents it from being clicked again.Attached patch fixes that. You may test with http://drupalcode.org/viewvc/drupal/contributions/sandbox/sun/core_tabs/
Comment #82
sunAnd a separate follow-up: #951262: Move #ajax default settings from PHP into JS
Comment #83
EvanDonovan CreditAttribution: EvanDonovan commentedIs the followup still critical?
Comment #84
chx CreditAttribution: chx commentedYes, but it's small and easy.
Comment #85
Dries CreditAttribution: Dries commentedCommitted the follow-up. Thanks.