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.
Drupal.js needs some love. Patch forthcoming.
Comment | File | Size | Author |
---|---|---|---|
#27 | core-js-drupal-cleanup-1089300-27.patch | 5.85 KB | nod_ |
#26 | core-js-drupal-cleanup-1089300-26.patch | 5.88 KB | nod_ |
#24 | core-js-drupal-cleanup-1089300-24.patch | 7.09 KB | nod_ |
#21 | 1089300-21-drupal-js-cleanup.patch | 9.22 KB | JohnAlbin |
#19 | 1089300-19-drupal-js-cleanup.patch | 8.38 KB | JohnAlbin |
Comments
Comment #1
ksenzeeAt the Drupal 8 JavaScript BoF in Chicago, we talked about how to develop JS patterns we can document and promote, and we decided we'd develop them gradually as we clean up the existing JS in core. So I'm documenting what I did here and why I thought it was a good idea. Please feel free to disagree with me. This kind of thing is notoriously intuitive -- a certain pattern just feels right or wrong, and it's hard to explain why. I'm hoping we can find good data to base our decisions on.
1. I'm suggesting a change to the documentation for Drupal.attachBehaviors. The example code passes an anonymous inline function to jQuery.once, and that's a style I'd love to move away from where we reasonably can. Inline functions are difficult to document, and they make the code flow harder to follow (unless you're a functional programmer, which most of us aren't). This one is also anonymous - it's not even
function atLeastIHaveAName () {}
- which makes the code harder to debug. As it happens, there's no real need to pass a function to jQuery.once in this example; it's just as easy to check whether any elements were returned, and act on them if so.2. For Drupal.attachBehaviors and Drupal.detachBehaviors, I replaced jQuery.each and another anonymous inline function with a for-in loop. Function-based iteration is the slowest type there is; for-in loops are a little faster. Since this code runs on every page load and every AJAX response, any performance gain would be good. (Regular for loops are even faster, btw - up to eight times faster than jQuery.each - so anywhere we can replace jQuery.each with a for loop, it's a performance win.)
3. In Drupal.checkPlain, we were using an object as a hash, which meant we had to use a for-in loop. I changed the data structure to an array, which means we can use the much faster for loop.
4. We were using the $() shortcut syntax for $(document).ready(), which I think is harder to read. To me the few extra characters are worth the extra maintainability, especially since we don't use $(document).ready() very often.
5. We were using an anonymous function to add to the jQuery.support object. I added it to the Drupal namespace instead. In general I'd like to see all functions living somewhere in the Drupal namespace. Some blessed day we'll get API module parsing our JS, and I'm sure it would appreciate it if our functions all have unique names.
My main concern at this point is whether I've broken anything in all this refactoring. I've tested each function in the Firebug console and it all works for me, but we desperately need unit tests. It looks like http://drupal.org/project/qunit already has some; I'm going to look into that and see if I can help move the effort along.
Comment #2
ksenzeeComment #3
ksenzeeFWIW, the existing unit tests from the qunit module needed updating, but once I updated them to pass on HEAD, they passed with this patch also.
Comment #4
mfer CreditAttribution: mfer commentedOn an initial read through the changes look good. Nice work. I'll need to apply them, test them, and see if there is any more we can do. Adding this issue to my todo list.
Comment #5
mfer CreditAttribution: mfer commentedI reviewed and made some updates. Some things I imagine we'll want to discuss.
Thoughts?
Comment #6
pascalduez CreditAttribution: pascalduez commentedHi,
this is really exciting to see efforts in pushing Drupal js into a more modern and robust way, and I would definitely like to give time for it.
I applied the patch in #5, and after some testing, no error / misbehavior so far.
Both patch in #1 and #5 looks really good improvements, compliments.
- Patch in #1
in Drupal.behaviors the function type test is gone, is it intended ?
A couple of quick ideas for the next steps :
- Should we advocate on the use of strict comparison operators everywhere ? There's some discussion around it in the js community.
- As there might be more and more functions / utilities added the the Drupal object, leading toward a sort of small framework, should we compare the different module patterns / inheritance APIs ?
Comment #7
mfer CreditAttribution: mfer commented@opalescent You bring up some good points so let me try to address them.
First, the removal of the use of $.isFunction. While I cannot speak for ksenzee, I am not sure I see a reason for isFunction here. There is nothing in the API that would put anything but a function on Drupal.behaviors.example.attach/detach. If someone did so it would be considered a bug in that code so throwing a JS error is OK by me. And, since we are making sure only properties on the behaviors are being looked at (via hasOwnProperty()) outside code should not slip in to make sure we avoid.
Second, the comparison operators. There is not complete agreement on the best approach. I did a quick analysis of jQuery, YUI, and dojo on this. Seems jQuery uses ===/!== for everything but comparisons with null where == is used. Dojo uses == regularly for comparisons. YUI intermixes the use of == and ===. I'm not yet entirely sure of what we should do other than to suggest we default to === and fall back to == when it makes sense to.
Third, the patterns. This issue and the set we are working on right now are to clean up the existing code. In the JS community initiative we've separated cleanup from changing the architecture for some better patterns. So, yes we need some better patterns. This is just not the issue to hash it out. We don't want to put too many changes in one patch/issue and the patterns one will require a bit of research, discussion, and finding agreement. So, it's separated out.
Comment #8
mfer CreditAttribution: mfer commentedAnother possible change we can make is to move _enhancejQuerySupport() off of the Drupal object into a private var in the closure. It used strictly for setup code, which should not be bassed a second time. Thoughts?
Comment #9
pascalduez CreditAttribution: pascalduez commentedThanks for clearing up all those points. I have to say I completely agree with you on them.
The removal of the use of $.isFunction, totally make sense to me too, we shouldn't write code that think in place of the end user, but rather transfer some responsibilities to him, and bring the tools, documentation to achieve it.
Comparison operators, I'll try to bring good infos sources about it, so we can consider the arguments for or against. But I like your suggestion. Use the strict as a basis, but stay flexible to use cases.
The patterns. Sorry, I did not wanted to rush things. I had read your good blog posts, and also the initiative page, but it did not came in mind last time, was too late maybe...
I can only say, thank you for writing such a clear and smart working plan.
Regarding _enhancejQuerySupport(): once again, make sense, no need to have it populate the Drupal object while it's called only once.
Comment #10
mfer CreditAttribution: mfer commented@opalescent I was thinking the exact same thing about _enhancejQuerySupport()
Comment #11
mfer CreditAttribution: mfer commentedThis patch cleans up some ==/===, moves enhancejQuerySupport() off the Drupal object (I imagine this might cause some discussion), and uses a DOM element to figure out positionFixed (which is faster than using a jQuery object). This is in addition to the previous changes.
Anyone up for testing/reviewing?
Comment #12
pascalduez CreditAttribution: pascalduez commentedPatch in #11 applied and tested (manually) and everything's fine in the browsers I tested in (FF, Chrome, Safari, Opera). I'll do some IE later on.
I'm particularly keen on the changes executed, so no critics from me.
Rewriting the enhancejQuerySupport() with native DOM elements/functions is what I've been planning to as well, so you was faster.
On question, although it seem to work nicely, the element is not injected inside the body element, which is not ready at the function execution time, but rather in between html and head. Just wondering whether this could be a concern or not. Does not seem so.
http://kangax.github.com/cft/#IS_POSITION_FIXED_SUPPORTED
https://gist.github.com/362170#L32
Comment #13
mfer CreditAttribution: mfer commentedLooking at what paul created, we should hide our element and account for page scroll. I'm wondering if enhancejQuerySupport() is now way more difficult to debug and understand. For the performance improvement I wonder if its worth it?
Comment #14
ksenzeeI finally got time to review this. Sorry for the delay.
#5-1: Yes, moving var declarations to the top makes sense, since JS does var hoisting anyway. I wasn't going to be strict about it but sure, works for me.
#5-2: The check_plain() improvements look good.
#5-3: I'm not a huge fan of the file-level closure pattern anyway. It's bad DX and it's never commented, so nobody really understands what it's doing unless they get functional programming to a certain degree. So they just copy and paste and grumble. But I won't argue about it, since theoretically someday we'll be minimizing our code and it'll help there. Maybe we could come up with a way of documenting it clearly and add a comment with a link?
#5-4: Yes, window.foo() is a good idea. In general I'm a fan of anything that helps people new to JS figure out what's actually going on.
#6/7: Yes, removing isFunction() was deliberate. If somebody adds something that isn't a function, the browser should choke on it. Otherwise we're hiding people's errors from them, which is maddening when you can't figure out why your code won't run.
#8: This is where it gets into a philosophical discussion. I would very much prefer not to have private functions and variables hidden inside file-level closures. What if someone wants to change the enhancejQuerySupport function? In general private variables feel quite unDrupalish to me. We tend more toward letting people override things. Since JS has built-in mechanisms for that, I'd rather make it possible for people to use them.
#13: The changes to enhancejQuerySupport seem a little beyond the scope of cleanup. It seems a little dicey to me that we're suddenly running the function as soon as the script loads, instead of waiting for the DOM to be ready. I'm wondering if we could leave that for a separate issue with its own discussion.
Comment #15
pascalduez CreditAttribution: pascalduez commentedTagging...
Comment #16
ericduran CreditAttribution: ericduran commentedThere might be a lot of behaviors in a given drupal site. Does it make sense to cache the hasOwnProperty lookup.
Something like this:
This isn't an actual review I was just looking at the patch and that caught my attention.
Comment #17
ksenzeeI agree #16 is a good idea; two-deep object lookup like that is slow. Technically it's also possible for someone to override Object.prototype in a way that behaviors.hasOwnProperty() will quit working, although that's really farfetched in a Drupal context. :)
Comment #18
nod_#16 not really, generally using
.call
hurts a more than a method lookup : http://jsperf.com/object-hasownproperty-vs-hasownproperty/2 needs some IE testing though.I'm not sure about the function check:
$.isFunction
has to go I agree, changing that totypeof
could be a good middle ground, it's a pretty critical part, it doesn't hurt to protect users against themselves. It's always possible to throw an exception or something too.The
$.support
thing should just go away, it's buggy #1439462: Drop our custom $.support.positionFixed and use jQuery's $.support.fixedPosition.Comment #19
JohnAlbinThe patch in #11 is extremely stale, so I rerolled. A good chunk of it was already implemented with drupal.js’s new Drupal.formatString().
Unless I'm misreading this code:
We are already running that function as soon as possible. Note that it isn't wrapped in $(document).ready(). mfer's cleanup just puts the enhancejQuerySupport name around it and cleans it up.
I never really understood the
(function ($) { // stuff })(jQuery)
stuff until I read JavaScript Module Pattern: In-Depth. If some bit of code is the right thing to do, but hard to understand, then we need some code commenting. I think http://drupal.org/node/224333#javascript_compatibility could use some improvement, for example.So, let's add some code comments.
Here's the rerolled patch in #11 without code comments.
Comment #20
JohnAlbinI just updated http://drupal.org/update/modules/6/7#javascript_compatibility to read:
[Edit: I reverted a part of that D6->D7 update since its not actually how D7's code looks. :-( But I moved that exact language to a D8 change record. http://drupal.org/node/1446420 ]
Comment #21
JohnAlbinI was going to add a link to the 6/7 upgrade, but that seems odd for D8 change. So I added a change record. http://drupal.org/node/1446420
Here's the patch with the code comment.
Comment #22
nod_Thanks for the reroll and all the documentation :D
Couple of things now that it's easy to look at the diff :)
There are changes that should be made globally, not just in drupal.js: #1428534: Use === and !==, #1428524: Replace all $.each() with filtered for loop and the way vars are declared. There are issues for the first two, for the last I guess we have to decide if we what that everywhere or not and if we use only one var on top or allow several var statements.
Ideally I'd go with one var but that's a very easy way to have leaking vars, it's easy to put a ";" instead of "," during the declaration. Since it's dangerous I'd say we go with 1 var per line and multiple variable declaration per line allowed.
The position fixed support addition is dead to me, i'd be happy if that was removed #1439462: Drop our custom $.support.positionFixed and use jQuery's $.support.fixedPosition.
For me this patch should be reduced to doc changes, function check in attachBehaviors, closure definition and check plain perf optimization. Good news is that we all agree on what should be changed :D
Comment #23
nod_There is now an issue for var declarations #1483396: [policy, no patch] JavaScript coding standards for variable declarations.
Comment #24
nod_reroll, I left out the === change because it's in another issue linked above.
Comment #25
sunLet's remove the See and URL. This does not really need further explanation, and people who want to find out why it exists can grep/pickaxe the commit history.
I'm fine with the remainder of the comment, as long as it is added in drupal.js only.
Can we revert these, please? We don't use typographic ellipses anywhere else in Drupal's source code base (except of human-readable strings being output).
Comment #26
nod_thanks, reroll
Comment #27
nod_woops, forgot the See+url
Comment #28
sunAwesome, thank you!
I wanted to mention we need a change notice for this, but the existing https://drupal.org/node/1446420 has been changed and referenced to this issue. I'm not sure whether that's wise. There's tons of JS code written for D7 already, and while the change notification is no functional change, I can see quite some confusion popping up in contrib (à la bug reports along the lines of "Improper JavaScript closure")...
Comment #29
catchThis looks great. I'm going to stick it in the change notification queue just in case, couldn't 100% parse #28.
Committed/pushed to 8.x.
Comment #30
nod_don't really get #28 either, change notif seems clear enough to me, can we close this?
Comment #31
nod_Comment #33
nod_