JQuery Update / Interface / Message Effects
Okay, so the story goes like this:
1) The SPAjax module (4.7 only) has a feature that allowed you to apply (Scriptaculous) Javascript effects to Drupal messages - allowing the "fade yellow" technique, pulsating, or shaking of the message divs. I missed this on my Drupal 5 sites and wanted to implement it in JQuery. Easy, right?
2) JQuery's effects library is called Interface. I couldn't find a version of Interface that would work with JQuery 1.0.1 (the version in Drupal core).
3) In order to update the JQuery in core, you need John Resig's compat-1.0.js script which allows backwards compatibility with scripts written for JQuery 1.0.x (such as everything in Drupal core). So now we need a module to include the compat-1.0.js file.
4) Interface.js is a relatively large file ~80K. It's "parts" (individual effects) are available separately, but this only increases the possibility that two modules will include the same (or worse two different versions of the same) scripts. So... now we need a central module to include a single interface.js file on the page when needed.
So here's what we ended up with:
- jquery_update is a module that facilitates upgrading core Drupal to JQuery 1.1.2 - it includes the compat-1.0.js file on every page and does some checking to make sure that everything has been put into the right places.
- http://cvs.drupal.org/viewcvs/drupal/contributions/sandbox/jjeff/jquery_update/
- jquery_interface is a module that provides a central location for including the JQuery Interface library to modules that need it.
- http://cvs.drupal.org/viewcvs/drupal/contributions/sandbox/jjeff/jquery_interface/
- messagefx is a module to add Javascript effects to messages (remember this!?)
- http://cvs.drupal.org/viewcvs/drupal/contributions/sandbox/jjeff/messagefx/
They're all in my sandbox right now. Before I create projects for these things, I wanted to get some feedback from folks on whether this seems like the best approach and/or solution to the problems outlined above. I saw that Nedjo had started a JQuery project with some of the same goals. But I never really liked how "glommed together" everything was with the JSTools package, so I'm hesitant to start sticking a lot of little pieces into one giant project.
Update
These modules have been committed to the Drupal contrib repository in the Drupal-5 branch. Get 'em while they're hot!


A couple of suggestions
Hey Jeff,
This looks good, and I think it's a good idea especially since the download page for interface library allows you to pick and choose which elements to include. So your #4 point about namespace conflict is an important one. I could create an interface.js file that is 24k that only includes the sortables, draggables & droppables elements -- and that could conflict with another interface.js file from the messagefx module.
Two suggestions.
1.) The way you've built this does allow me to replace the 80k interface.js with a smaller 24k interface.js version if I didn't need the other fx elements. But if I do this, then problems do come up when I want to use other modules that use the interface.js file.
So I think it'd be a good idea for each module that uses this interface library to list the essential elements that it needs for it to work. That way users could have the option to go roll their own interface.js file by going to the download page and picking and choosing which elements they actually need. I know that I'd rather load a 24k js file than a 80k file if I had the option to do so. And if I want to use other modules, then I'd like for it to be really easy to re-roll the interface.js file with the elements that it needs for the new module to work.
2.) I think that there might need to be some more info in the jquery_update readme.txt for what the best practice is for upgrading Drupal if you replace the core jquery.js file. Do we have to replace the jquery.js file with the original 1.0.1 file when upgrading Drupal? If so, then maybe there should be a link to the original jquery file in the readme.txt file -- http://cvs.drupal.org/viewcvs/checkout/drupal/drupal/misc/jquery.js?rev=1.6
1.) The way you've built
Well technically, if you replace the interface.js file, you're hacking the module.
I like this idea, but I'm not sure that the overhead is worth it. My vision for this is that there would be a subdirectory containing all of the "pieces" of the Interface library. Drupal modules that wanted parts of Interface could declare a array list of elements and then the jquery_interface.module would only add these elements.
Problems:
- If there are several modules that have several needs (drag-and-drop, fx, tooltips, etc), then the module could end up including 10 or more .js files on the page.
- The logical fallback would be to allow no arguments to the jquery_interface_add() function and default to including the entire library. If there was just one module that did this, then the whole "separate files for efficiency" thing gets thrown out the window.
- It's probably better to have one 80K file, than to have 5 18K files. With the compression scheme used by Dean Edwards' packer, you're going to get better compression on one large file than several smaller ones. Keep in mind that whatever the size of these files, they're going to get cached on the browser end, so it's only going to download once.
- Drupal 5 does not have a JS aggregator (similar to the CSS aggregator) so each of the JS files will be a separate hit on the server.
Yeah. Here again, technically speaking, we're hacking Drupal core by replacing the jqeury.js file. It's hard to talk about "best practices" when we're doing this... it's going to be more like "better practices". I've got some ideas for how to guide people through updating core, but it's still going to be a relatively hairy situation. I REALLY wish that there was a way to manipulate the drupal_add_js() array so that this module could simply tell Drupal NOT to include the core jquery.js. This would solve all of the core update problems, but I haven't found any way to do this.
Declaring elements allows for advanced use
Hey Jeff,
I agree that breaking up the individual elements into separate javascript files is neither a good nor practical idea.
What I was suggesting is an advanced use of being able to swap out the interface.js file. But in order to do this, then each module that uses the interface library would need to declare which interface elements are essential for it to work.
For example, if messagefx only uses the shake, highlight & pulsate interface elements, then it's possible to create an interface.js file that only includes these 3 elements and is only 16 kB big. The interface download page saves an "interface.js" file to your computer whether there is one element or all of the elements selected.
So if another module only uses the sortables, droppables & draggables elements, then that interface.js would be 24kB
If you wanted to use both modules, then you could go to the interface download page and check the 6 elements to create an interface.js that is only 28kB big.
Defaulting to 80kB file is the universal solution that you currently have, but it'd be nice to have each module that uses the interface library declare in the readme.txt or install.txt which interface elements are essential for it to work so that advanced users could roll their own interface.js file if they chose to do so.
overoptimize
i think it is not so important to save a few bytes like that. for what purpose? bandwidth minimization? the files are cached on browser side for a long time so this is a one time deal, per user.
JS aggregator
Ted and others have been working on a JS aggregator that could take care of packaging the multiple files. Check it out here if you haven't seen it yet:
http://drupal.org/node/119441
It seems like having all of the Interface components available and separate and allowing the modules to pick which ones they require and then bringing them all together with the JS aggregator would be the ideal way to handle it.
Chris
ALIAN DESIGN
JS Aggregator is for Drupal 6
That will be a solution for Drupal 6, but we do not have JS aggregation in the current release of Drupal.
Also, Drupal's JS aggregation does not (currently) use Dean Edwards' Packer (nor am I convinced that it should), so a single packed file of the entire interface.js file may end up being smaller than several of its component pieces packed on their own.
policy?
Does anyone know if there has been any resolution to the don't-include-plugi-ns-with-your-module thread? Having to explain to users how to pull the right version of a plug-in out of the jquery svn repository is a bit of a buzz kill.
Also, I remember seeing on the dev list that interface.js would never be included in the drupal distribution. Does any one know why? Having interface in D6 would solve a lot of problems.
I'm for a single module-per-jquery-plugin
I'm of the opinion that since Drupal handles modules (and module dependencies) so well, it's probably best to just have a Drupal module that includes the necessary JQuery plugin. Reasons:
1) Central location for the plugin code and therefore less chance of multiple modules including the same (or worse different versions of the same) JQuery plugin.
2) Drupal can handle dependencies.
3) There can be different Drupal module releases for the different versions of the JQuery plugin. The module that implements the plugin can provide a version number that can be used by hook_requirements() to further make sure that the correct version of the module is installed (I don't think that the normal module dependencies system can do this).
4) There could conceivably be settings and/or special ways of including the plug-in code on the page. This is easy to do using a specialized module.
Hopefully, the jquery_interface.module can be a model for this system. It seems to me like the best way to go.
Differing opinions are welcome... as long as they are not wrong. :-)
Makes sense
This approach makes sense. The only downside I can see is that it is annoying for someone to have to download 3 other modules for own of my modules to work...but I think it is worth the trade off. (The other downside is if it gets us banned from using the the drupal cvs repository, but I will leave that fight to people more political capital :)
So, does this mean your vision in D6 is that jquery.js gets moved from the misc directory into modules/jquery, where it can be overridden by a module with a newer version in sites?
CVS banishment and JQ4D6
How/why might that happen? I've been known to be ignorant of specific CVS policies in the past. Is there something I'm missing?
I'm making the assumption that the JQuery plug-in code is GPLed, otherwise it will need to be downloaded separately (and a module could help to make this process easier too).
Actually, no. I would like to see the array that gets built by drupal_add_js() function be something that modules could manipulate similar to the way that hook_link_alter() works. I haven't thought this out fully, but it simply would be nice to be able to suppress the inclusion of several of Drupal's core JS files, not limited to jquery.js, but perhaps one might want to redefine (or exclude altogether) collapse.js, or upload.js, etc..
In it's simplest form, this means that a module could update JQuery without having to "hack" core at all.
The CVS issue seems to be
The CVS issue seems to be Gerhard doesn't want any 3rd party code in the repository, even if it is GPL'd. I don't understand why myself.
http://drupal.org/node/124978
I think it's important to
I think it's important to make drupal contrib modules with external dependencies usable by non-developers. If a maintainer(s) can commit to maintaining 3rd party GPL code then why not incorporate it into cvs.drupal.org. An alternative would be automatically downloading the library from external source and installing it as OS package management systems do.
No 3rd party code?
What the heck is JQuery? Sharing is kinda the whole point of the GPL.
People need to stop smoking crack. Seriously. Crack is bad.
I think the main idea was to
I think the main idea was to ensure that we don't have code written by people from outside the Drupal community in our CVS, since it can get out of date, leaving modules that rely on obscure old versions of 3rd party code, and can also potentially include security holes that are harder to find. This has been the policy for a long time (witness TinyMCE, phpFreeChat, the extended date library - all of these have long required downloading and extracting 3rd party code).
While this avoids the problems above, it causes others - such as lots of version mismatch problems and making it much harder for newbies to install these modules.
With the new release system I think that it is time for this to change. We can now quite easily match the third party code to a particular Module release, which should make it easier to both make sure the 3rd party stuff gets updated, and also to avoid the version conflicts. The security issues (for the people actually running the modules + scripts on their server) will be the same regardless.
Ugh
I have been testing your modules, and it looks like it is not just a simple drop-in replacement. It causes my Ahah stuff to hang. And simplemenu isn't looking too happy with it either. I will let you know what the issues are after some debugging.
Also, you might want to change the default values in messagefx. If you forget to copy over jquery.js, messagefx's pulse effect ironically hides the error message that jquery_update sends.
JQuery Compat 1.0
According to John Resig "properly written" scripts for JQuery 1.0 should be made compatible with JQuery 1.1 when the Compat-1.0 plugin is installed.
What exactly "properly written" means is the area that's certainly up for discussion. :-)
Here's some links for you:
http://docs.jquery.com/Plugins
http://dev.jquery.com/browser/trunk/plugins/compat-1.0/
Let me know how this works out for you. This code is supposed to be making things EASIER, not breaking things.
I would love to connect with you to make sure we've got solid solution going here. Perhaps we could do a conference call to try and come up with the best practice solutions for all of this.
Tracked it down
The problem is here
Ahah.my_function = function( arg1 ) {console.log( "arg1 = " + arg1 );
}
if( Drupal.jsEnabled ) {
$(document).ready( Ahah.my_function );
}
This code used to print out "arg1 = ", which is what I was expecting. Now with jQuery v1.1, it prints out "arg1 = function(a,c) { if ( window == this ) ...". Arg1 is being set to the jQuery global. If I do the test arg1 == jQuery, it returns true.
I don't know if this is a jQuery bug, or just a usage issue, but it is an idiom used regularly in Drupal (autocomplete.js, upload.js, textarea.js), but none of their attach functions have optional parameters, so they should be ok. Anyway, the solution is pretty simple:
if( Drupal.jsEnabled ) {$(document).ready( function(){Ahah.my_function()} );
}
Y'know, as weird side-effects go, this one could be useful. I had some trouble getting FireFox to switch to the new jQuery. Shift-reload didn't do it, so I had to clear out the cache using the "Clear Private Data" tool. Which could cause a lot of bug reports for modules built on top of your update modules. So you could add a small jquery_update_check.js file to the jquery_update package and drupal_add_js it in jquery_update_menu. Then all it would need to do is call a function in the first way, and then if arg1 != jQuery, pop up an alert box telling them they still have the old version of jQuery cached.
BTW - the issue with simplemenu is that 3rd level menus are popping out prematurely, when only 2nd level menus should be triggered. I don't know what is causing it, but it isn't the same issue as my stuff (I'll let you bring it up with Ted :)
I am happy to be part of a conference call on the subject.
Collapse?
Hey Jeff,
I'll test this out later, but are we lucky enough to get a solution for the issues with collapse.js and the backwards-compatibility plugin?
http://groups.drupal.org/node/2970#comment-8528
http://www.chapterthreellc.com | http://www.outlandishjosh.com
collapse.js
We will be when someone writes it!
Right now it appears that collapsible fieldsets will open, but not close. It seems like there would probably be an easy fix for this and my guess is that we could overwrite the function pretty cleanly. But I haven't had (and don't anticipate soon) a chance to sit down and figure this out.
How 'bout you? Or Tao?
As soon as we fix the collapse issue, I'll commit the module as a Drupal project.
I'll bite
I will take a look and see what I can do.
Yes!!!
Thanks Tao. You're the man!
The patch
Ok, it was a little tricky to track down, but it's a one line fix. With some trepidation, I submitted it over here: http://drupal.org/node/120291 as http://drupal.org/files/issues/collapse.js_.patch
Yes but...
Okay... So, what's the best way to do this without patching core?
Can we just make a collapse-fix.js file that overwrites this attribute? Since our module-based js will output to the page after the core js (right?), we can cleanly overwrite the function.
I'm guessing that we don't need to include an entire patched version of collapse.js, or am I wrong?
Tao, can you distill down the patched version of collapse.js into just the stuff that needs to overwrite the broken stuff in collapse.js? Geez, what a confusing sentence. Let me restate:
I want to include a collapse-fix.js file with the JQuery Update module. This file will simply redefine the broken parts of collapse.js. Can you submit the code that would be in that file?
Sure, but...
Hmm, doing it that way won't you open up the possibility of a race condition between $(document).ready in collapse.js and our redefinition of Drupal.toggleFieldset. I don't know if the timing is deterministic or not. How about not trying to overwrite the function, and instead just redo the binding?
jquery_update_fixes.js:
<?php
var JqueryUpdate = JqueryUpdate || {};
JqueryUpdate.toggleFieldset = function(fieldset) {
if ($(fieldset).is('.collapsed')) {
var content = $('> div', fieldset).hide();
$(fieldset).removeClass('collapsed');
content.slideDown( {
duration: 300, // this is the change from collapse.js in D5x
complete: function() {
// Make sure we open to height auto
$(this).css('height', 'auto');
Drupal.collapseScrollIntoView(this.parentNode);
this.parentNode.animating = false;
},
step: function() {
// Scroll the fieldset into view
Drupal.collapseScrollIntoView(this.parentNode);
}
});
if (typeof Drupal.textareaAttach != 'undefined') {
// Initialize resizable textareas that are now revealed
Drupal.textareaAttach(null, fieldset);
}
}
else {
var content = $('> div', fieldset).slideUp('medium', function() {
$(this.parentNode).addClass('collapsed');
this.parentNode.animating = false;
});
}
}
JqueryUpdate.clickLegend = function() {
var fieldset = $(this).parents('fieldset:first')[0];
// Don't animate multiple times
if (!fieldset.animating) {
fieldset.animating = true;
JqueryUpdate.toggleFieldset(fieldset);
}
return false;
}
if (Drupal.jsEnabled) {
$(document).ready(function() {
$('legend a').unbind('click').bind('click', JqueryUpdate.clickLegend);
}
}
?>
If you want to try it out your way, all you need is the .toggleFieldset function, named Drupal.toggleFieldset.
Hmmm... Maybe I'm not
Hmmm... Maybe I'm not understanding things correctly. But can't we just redefine previously defined functions in Javascript (unlike PHP)?
In this way we would just be redefining the one "wrong" function in Drupal rather than having to hijack the clicks on the legends. Javascript will just execute the latest definition of the function, no? More like CSS in this way, rather than PHP.
This is untested, but couldn't we just provide a collapse-fix.js file that looks like this:
<?php//(not really PHP, but just provided for syntax hilighting)
Drupal.toggleFieldset = function(fieldset) {
if ($(fieldset).is('.collapsed')) {
var content = $('> div', fieldset).hide();
$(fieldset).removeClass('collapsed');
content.slideDown( {
duration: 300, // THE FIX
complete: function() {
// Make sure we open to height auto
$(this).css('height', 'auto');
Drupal.collapseScrollIntoView(this.parentNode);
this.parentNode.animating = false;
},
step: function() {
// Scroll the fieldset into view
Drupal.collapseScrollIntoView(this.parentNode);
}
});
if (typeof Drupal.textareaAttach != 'undefined') {
// Initialize resizable textareas that are now revealed
Drupal.textareaAttach(null, fieldset);
}
}
else {
var content = $('> div', fieldset).slideUp('medium', function() {
$(this.parentNode).addClass('collapsed');
this.parentNode.animating = false;
});
}
}
?>
I guess the next step would be to drop that file in and see what happens...
If this works, we'd avoid the race conditions and all that, right?
Tested and working
Okay, I've tested this and it seems to work fine (no errors in Firefox) as long as the collapse-fix.js file loads AFTER collapse.js. It looks like core JS files load after module JS files, so I've had to include it as a "theme" JS file using the following line:
<?phpdrupal_add_js($path .'/collapse-fix.js', 'theme');
?>
A little bit of a kludge, but pretty clean other than that.
Again, I could very well be missing something here, so please let me know if I'm heading off in the wrong direction here.
I've committed the collapse-fix.js file and the new version of jquery_update.module to my sandbox. If I can get a couple of thumbs up, I'll make a project and create a release.
jQuery_Update works good!
jQuery works good, no collapse problem anymore!
...though I'm using my own textarea.js patch (http://drupal.org/node/118846) so I can't say if it works on unpatched textarea.js files well.
The other two modules I haven't tested b/c my theme heavily uses selected Interface.js files (don't have time to update all links now).
Btw, it's quite circumstantially to save files from ViewCVS by clicking each Revision link ... or I'm too nooby to see a faster way. ;)
Thx for this mod!
I am probably over-thinking
I am probably over-thinking it, but what I am worried about is $(document).ready. I know it is designed to be really aggressive, to trigger as soon as the DOM is loaded, and that it has per-browser tweaks to find that moment. Does anyone know if it waits until all the JavaScript files have loaded? If not, then the binding in collapse.js could happen before Drupal.toggleFieldset is overwritten.
Like I said, probably over-thinking. All that really needs to happen is for someone to test your approach in the 3 (4?) major browsers and if it works then it's all good.
Sloppy Science
[educated guess] I think it's okay if the overwrite happens after the binding. The bindings are a reference to the original function. If the original function gets overwritten, the bindings should change too. [/educated guess]
That's my hypothesis... and I'm sticking to it! :-)
Okay, so I also did some testing and I think we're good. Here's what I did to test:
1) I removed comments and converted the entire to collapse-fix.js to a single line (you'll see why in a moment).
2) I removed the drupal_add_js() for collapse-fix.js from jquery_update module
3) I loaded a page with collapsible fieldsets -- sure enough they won't close.
4) I used the FireBug command line to execute the code from collapse-fix.js. This is why it needed to be a single line. Note that the page has already loaded and the bindings have already been made.
5) The fieldsets work!
Hooray for the scientific method!
Disclaimer, Aristotle would probably actually test in all browsers. I'll just wait for bug reports. :-)
Cool Beans
Sounds good to me.
Just about the glom. I'm a
Just about the glom. I'm a fan of the jstools all-in approach for this type of thing. I have jstools on-hand all the time, although I only use 20% of it, it would be nice to have messagefx in there as well. ;-)
I agree that one download is
I agree that one download is nice. But each of these really are distinct projects with their own distinct bugs and issues. I think that they'll be a lot easier to maintain with their own specific issue queues rather than one giant pot of bugs.
Understood, actually this is
Understood, actually this is a problem with ecommerce I find, lot's of disparity b/t supported and unsupported modules in the one distro.
Best of both worlds?
I agree that having one issue queue for a bunch of distinct projects is really annoying. We did away with that crap in organic groups module long ago. However, it's really great for users (and developers too) to only have to download one module (JSTools) to get all of your effects and whatnot.
So what if we did this like the Location Views project, which is in a "contrib" folder in Location module?
Subprojects?
Hmmm... Is there a way to have projects refer to a subdirectory of another project? In this way each "subproject" could have their own issue queue, but they would be downloadable from the larger "superproject".
Doh!
I should really read your whole message before posting. I guess it can be done...
Hmmm...
Jquery Update broke my collapsible fieldsets - really!
Hey there :)
I was delighted to see that there was a jquery update that tied into the interface elements and messagefx. Thanks for all your hard work.
It took me a while to notice since I was distracted by all the cool Message Effects, but after installing the Jquery Update module (and copying over the revised jquery.js included with the module) that my collapsibile fieldsets no longer work. Any fieldset that is collapsed by default (as are most CCK fields) just shows the legend text without any link or arrow to click on in order to expand the fieldset. So I can't even get into those fields.
I tried downloading/installing several times and made sure to copy the new jquery.js into /misc/jquery.js. I also emptied the cache, etc. and still no luck.
The only thing that fixed the issue was reverting back to the old jquery.js (the one that comes with Drupal 5.1).
I'm surprised that not many people have raised this issue as I'm assuming everyone would have installed the jquery update module and gotten the same results.
Or are people not using collapsible/expandable fieldsets?
is there any way to get a readable copy of both the drupal core version and the jquery-update version of jquery.js so I can try to figure out why this is broken?
many thanks in advance,
brian
same here
with Drupal 5.x , Jquery update and jquery 1.2.6
subscribing for a solution
Followup on http://drupal.org/node/142216