Closed (fixed)
Project:
Drupal core
Version:
7.x-dev
Component:
javascript
Priority:
Critical
Category:
Bug report
Assigned:
Issue tags:
Reporter:
Created:
27 Aug 2009 at 23:36 UTC
Updated:
12 Jun 2010 at 00:29 UTC
Jump to comment: Most recent file
Comments
Comment #1
sunAnd, yes, this is critical, and we cannot release without fixing it.
Comment #2
sunTo learn more about detaching behaviors: #316225: Allow behaviors to detach from AHAH/AJAX contents
Comment #3
effulgentsia commentedHere's a patch. This can be tested with the iframesrc module from http://drupal.org/node/561796#comment-2139256. Not exactly a "useful" module, but hopefully, at least useful as a test-case.
Here's what the patch does:
This means that in the use-case of "add another item" of the iframesrc field (and more importantly, a wysiwyg enabled textarea), detachBehaviors() is called twice (once before the form is serialized, and once before the wrapper is removed) and attachBehaviors() is called twice (once after the new content is added, and once in the success() handler). I'm assuming this isn't a problem, since behaviors use the convention of a BEHAVIOR-processed class name to avoid redundant attaching/detaching. I don't see an immediate way around this (non)problem, since we don't want to assume any correlation between the wrapper and the form.
The other issue I can foresee is that it might be annoying to "detach" everything within the form, especially if the AJAX will only affect a small part of the form (for example, here we detach behaviors for all elements within node-form, even though "add another item" only really affects that field). However, only by detaching everything within the form can we be assured that wysiwyg has a chance to have the form values populated correctly. By "annoying", I mean that if wysiwyg implements detach() as a reversion of the wysiwyg editor to a simple textarea, then this would result in a lot of flickering due to all wysiwyg editors on the form reverting to the textarea at the start of the AJAX call and then reverting back to the editor at the end of the AJAX call, even if the AJAX call doesn't affect that field.
What this points to is that we may want to have two modes of detach. Something like "fully detach", in which case the wysiwyg editor would fully revert to a simple textarea, and "partially detach", in which case the wysiwyg editor would save its content to the textarea, but not need to fully revert to the textarea. This partial detaching could be done fast, and would mean that there wouldn't be a situation of all wysiwyg editors visually disabling and re-enabling, every time "add another item" is performed on an unrelated field. What do you think, should we add a 3rd parameter to Drupal.detachBehaviors() to support the distinction? Or can we assume that "partial" is all we ever need, and that there's no use-case for "full", in which case, no new parameter needed?
Comment #4
sunIntroducing a new tag for feature freeze: API clean-up.
Comment #5
sunTagging absolutely critical clean-ups for D7. Do not touch this tag. Please either help with this or one of the other patches having this tag.
Comment #6
effulgentsia commentedConfirming that #3 still works for me. @sun: I'm still curious about what you think of my last paragraph in that comment.
Comment #7
effulgentsia commentedWhile the text of comment #3 is still relevant, there's an updated tester module: http://drupal.org/node/561796#comment-2139256.
Comment #8
effulgentsia commentedI updated comment #3 to have the correct link to the tester module.
Comment #9
sunWe absolutely need to pass the local AJAX settings to attach/detachBehaviors() here
I'm on crack. Are you, too?
Comment #10
effulgentsia commentedDiscussed with sun and DamZ on IRC, and will soon implement and summarize the thinking. Leaving as "needs review" because more reviewer feedback is still welcome.
Comment #11
sunAlighty... what we want to do:
Next to passing settings to Drupal.detachBehaviors(), we also want to pass one further argument that gives detaching behaviors some clue about what is happening. In the case of Wysiwyg, we have two different types of "detaching":
1) The form (or some other section of a form) is about to be submitted via AJAX. Here, we don't need to detach and unload all editors, but instead instruct them to save the editor contents back into the form elements, so the values are properly submitted.
2) The form (or the part of a form) is a about to be replaced with fresh content. Here, we need to detach and unload the editors within the context, because the DOM elements are about to be removed.
We want to do this, because what we currently have maps to 2). But his means, for example, when considering a form that contains a regular body field, but also another textfield that is multiple and has a "Add more" button, then clicking that "Add more" button will trigger full detaching and unloading of all editors in the form, so the server-side gets all form values.
With the distinction of 1), we basically have a two-step detaching process that could be understood as "saving" (detaching) and "unloading".
Comment #12
effulgentsia commentedThanks sun! In addition to what you already said, there's a 3rd type: "row swap", or more generically "dom move". I think we should incorporate the change needed to tabledrag.js (#561796: tabledrag.js does not implement Drupal.detachBehaviors()) into this issue because of this. A "dom move" is different than a "dom remove" followed by a "dom insert", because it appears only iframes get messed up during a dom move, and non-iframe tied behaviors wouldn't need to do anything to deal with a row swap.
Comment #13
effulgentsia commentedHere's a patch with both the ajax.js and tabledrag.js changes. Unfortunately, I am not familiar enough with the whole local settings thing to decide what to pass in for that parameter. To help search, I added
nullfor that parameter. Can we get quicksketch or someone else who understands how local settings work to look at this and advise on what's needed for local settings in each line that invokes Drupal.attach() and Drupal.detach()?Comment #14
effulgentsia commentedApologies for the "?" lines at the top of the previous patch. Here's the same one with those removed.
Comment #15
effulgentsia commentedOops. This one fixes a mistake in the @code JSDoc for Drupal.attachBehaviors. See #13 for my note about local settings param.
Comment #16
sunJSDoc summary needs to be on one line and a summary, optionally followed by a description.
This is rather a description, so we need a separate summary.
JSDoc standards mainly follow PHPDoc standards, see http://drupal.org/node/1354 for more information.
Please use a lowercase identifier ('data') and single-quotes here.
Aren't the local settings stored in ajax.settings or similar? I thought the other patch in the queue about AJAX settings fixed that.
Blank lines should be blank.
Please skip the listing of reasons here and just say "See Drupal.attachBehaviors() for details."
I'm not really sure whether we really need this. I hope that TwoD can help to figure this out.
I'm on crack. Are you, too?
Comment #17
effulgentsia commentedWith #16, except:
1) I've had 0 experience with all the work that went into local settings. I think it's great, but I don't think I'm the right person for the job of figuring that out and figuring out how to test it. Settings data, element data, and AJAX data seems to be merged and massaged all over the place. It probably makes sense to someone, but not to me yet.
2) If TwoD can figure out a way around the need for 'dom_move' triggered detach/attach, great! The reason wysiwyg for D6 breaks when you row-swap (at least on tinymce on firefox does) is the IFRAME problem in the JSDoc above Drupal.attachBehaviors(). I had to do some nasty hacking to make D6 wysiwyg work within a cck multi-valued field: http://drupal.org/project/wysiwygcck, and I know from my experience on that module that this would fix it.
Comment #18
katbailey commentedsubscribing
Comment #19
twodThe issue sun mentioned earlier is probably #595654: AJAX command 'settings' broken, without that patch, these fixes will have little effect.
In the patch below, I've just added the settings arguments to the last patch. I followed the (
var settings = ...pattern in existing calls. Technically, we could skip the|| Drupal.settingspart but having all of it there might make things clearer.After spending half the night testing this using both the iframe module and a modified version of Wysiwyg module I think this does a pretty good job fixing this issue. I think this concept is RTBC, but there are a few details I'd like to bring up before actually marking it as such.
If I understood the above discussion, the 'reason' argument was added to give behaviors a way to determine if they always need to do a full detach/attach, and Wysiwyg was the main target because of the editor iframes. As far as I can tell, iframe-fields always need to fully detach/attach on 'dom_move', no easy way around that. Other field types won't be hurt by the swapping routine as they keep all relevant states, so 'dom_move' could be dropped in favor of a full detach/attach.
I like the ideas about the pre form serialization step ('data'), anything submitting forms can rely on widgets 'dumping' their raw data on their own and in time. It seems to work pretty well too as is. We could even put this in its own 'serialize' behavior and there would be three distinct operations; attach, serialize and detach. In its current form, there could of course also be other reasons added by other modules, but they can't really control what responds to which reasons without further hacking. Just a thought...
Sorry for my delay [and ranting] on this guys, life seems to have its way to get in the way hehe.
Comment #20
twodOh, there's an attach button too...
Comment #21
sunI'm equally not sure whether 'dom_move' is really needed. The only use-case I could see is that an editor (or whatever else) may not have to fully re-initialize itself, because we just detach from the DOM, but do not unload completely. Fully detaching would potentially mean to also remove the corresponding settings and data stored elsewhere, while in 'dom_move', all of that could be kept (if supported).
So, let's give this some final round of sleep and thoughts before marking RTBC. No reason to hold off discussion and sharing of thoughts. :)
Comment #22
effulgentsia commentedThanks for the work on this and its dependency issue, TwoD!
I'm fine with keeping it as-is with respect to the "reason" parameter. However, if there's a concern that it creates unnecessary complexity for people writing behaviors to understand, then given what you said, I can envision your other thought as working: drop "reason" and add "flush" (I'm open to a better word choice, but that's what I've come up with so far), so we would have a Drupal.flushBehaviors(), and each behavior could implement an optional "flush" function. Two things would need to be true for this to work:
1) That developers writing behaviors that implement a "detach" function are ok with that being called before a dom move -- i.e., that we're not anticipating some kind of behavior that needs to detach before the dom element is fully removed, but that wouldn't want to do so before being row-swapped within tabledrag. I guess I'm just paraphrasing what sun said in #21.
2) That behaviors can implement a "flush" without needing an additional callback when the form is done submitting (i.e., we could drop the attach calls from success() and error()). This, I actually think should always be true. Even in the case of a hypothetical editor that can only save its data by fully unloading, I suppose there's no reason why it couldn't fully unload and fully reload within the same flush() call. Besides, I think both tinymce and fck support a way to flush data quickly and without flicker, and any editor that doesn't support that, perhaps we just shouldn't be using.
If we're ok with both of these conditions, I can definitely see the appeal of 3 methods instead of 2 methods that each take an extra parameter that can be 1 of 3 values.
Comment #23
twodHmmm, that's right... If a behavior fully detaches before a swap it should clean up after itself, including all settings. Unless we temporarily store a deep copy of the settings during the swap it might not be possible to attach something again. The attach behaviors should not take harm from being fed data once 'deleted' by detach behaviors, but is that something we could get away with? It's probably more fair to let the developer decide what to do in this situation. The swap is a special case, so how special do we make the workaround?
I think I'm fine with keeping the 'dom_move' reason as a flag to partially or temporary detach/suspend a widget as storing a deep copy of the settings and making swapping a behind-the-scenes-operation might lock it in too much for the developer. We might encounter other situations where similar to tabledrag might need to swap/move elements, and then it would be pretty handle to be able to signal a partial detach.
Case 2 should indeed be true. I don't know of an editor not capable of flushing the contents or be forced to do so without fully detaching (editors that don't want to detach at all seems more common). Editors can't on the other hand detach and attach in the same call if they rely on iframes or other asynchronous operations, but that we should not have to consider since they are able to update the original field anyway.
Comment #24
sun1) Introducing a new "unload" or "flush" method, or the earlier mentioned "serialize" method for behaviors is too much for D7, because we would have to entirely rewrite the Drupal.attachBehaviors and .detachBehaviors methods. That sounds like interesting stuff we should consider for D8.
2) Now that we talked about it, we should rename "reason" to "trigger". Then, "trigger" is one of the following: "unload" (default), "move", or "serialize". The last one is debatable, because the others nicely map to DOM events and only "serialize" would map to a jQuery Form's serializing. But it makes far more sense to me that way. Could also be called "event", but then developers might expect a DOM event object.
In case #557272: FAPI: JavaScript States system will get in, we might even add further detaching triggers.
Comment #25
effulgentsia commented@sun: I'm probably missing something, but I'm not following your point #1. What's wrong with the attached patch?
It also occurs to me that attach() doesn't need a trigger, because detach()'s trigger-specific implementation can setup the needed state variables to track the element state, and attach() can react to the element state.
Comment #26
sunIt's too much for D7, because we still need the trigger argument for detachBehaviors(). As mentioned before, we should revisit this entire construct more in-depth in D8.
I'm on crack. Are you, too?
Comment #27
sunSo what we quickly need is the patch from #20 merged with the name changes from #25
Comment #28
effulgentsia commentedOK. Do you agree, however, that only detach() needs the trigger, and attach doesn't? That's how this patch is structured.
Comment #29
effulgentsia commentedActually, this one restores the attach() done in success() and error(). My thought is that a behavior that implements the serialize trigger correctly won't need to do anything on attach in these places. However, a simple behavior that implements a detach without taking the trigger into account will need an opportunity to reattach.
Comment #30
sunLooks solid to me.
Only thing I'm not sure about is whether attachBehaviors() shouldn't equally pass "load" and "move", like it was contained before. It's possible that attaching behaviors can figure this out on their own, but again, not sure.
Comment #31
effulgentsia commentedLet me know if you want them back in, and I'll do it. I can't think of a use-case where the attach() function couldn't figure it out, given what the detach() function ended up doing, and it saves us from figuring out a name for the trigger that success() and error() would pass.
Comment #32
effulgentsia commentedBasically, I don't think the attach() should care what triggered it, it should only care about what is the element's current condition, and what needs to happen for the behavior to be fully operational.
Comment #33
sunYeah, I agree. Otherwise, we'll find workarounds. ;)
Comment #34
twod#29 looks good to me too.
Comment #35
sunVery very minor JSDoc changes.
Comment #36
rfaySubscribing
Comment #37
webchickHm. I don't really get what this patch does, but all the smart JS people want it. :P Therefore, committed to HEAD. ;)
Comment #38
twodWohoo! Thanks webchick! What it does is pretty simple. Our JS widgets can now 'jump out of the way' when the user [re]moves elements on the page and translate any changes made, temporarily stored in the widget's state, back to the original element's state.
It would not be so easy for the code doing the [re]moving to handle all kinds of widgets, so now it doesn't have to. =D
Comment #39
effulgentsia commentedwebchick: I suspect you get much more than you let on, but if you're interested in the non-techy translation of #38 to share with people you talk to about this, this fix means that unlike in D6, WYSIWYG editors in D7 won't break when used on "unlimited" values text fields.
Comment #40
sunGuys, you rock! :) LOL
Comment #42
effulgentsia commentedFYI: there's a follow-up issue related to the code that was added in this one: #825318: Drupal.attachBehaviors() is called an extra time, unnecessarily and for document context, when there is no form.