Conflicting jQuery plugins

Permalink 1 user found helpful
I have two different blocks that both include the same jquery plugin (a lightbox effect). If both blocks are on 1 page, that lightbox effect gets outputted twice, which causes it to break.

One solution I thought of was to check if the plugin has been loaded already. But the problem with this approach is that while I know how to check in javascript if a certain jquery plugin has been loaded, I don't know how to use that to conditionally include other javascript files (since you include js files from a <script> tag, not from javascript code itself).

Has anyone run into this problem before or have an idea for a solution?

Thanks.

-Jordan

jordanlev
 
Shotster replied on at Permalink Reply
Shotster
Hi Jordan,

That is precisely the type of thing I anticipated happening at some point when I first started using C5 and even mentioned it in an early post. Basically, the C5 mandate that "everything" required by a block be included in that block is not ideal for JS libraries and library plug-ins or extensions. It really only makes sense to include truly custom code that's unique to the block (or package). Ideally, C5 should have some type of dependency mechanism that checks for (and even automatically installs) common libraries and shared JS modules that are needed by a block.

Anyway, to answer your question about dynamically loading JS files, that's precisely what I did here if you recall...

http://www.concrete5.org/community/forums/customizing_c5/where-to-p...

-Steve
jordanlev replied on at Permalink Reply
jordanlev
Touche!
But actually the technique in that thread wouldn't solve the issue I bring up here -- sharing common js between all code in a package is no problem, but what do you do if there's another block or package that's not under your control (or is in the theme itself)?

And the more I think of it, I don't think it's solvable by the CMS, because you don't have any way of uniquely identifying a jquery plugin (for example) by its filename -- it may be "lightbox.js" or "jquery.lightbox.js" or "jquery.lightbox.1.3.2.min.js", etc. etc.
I think the only way to identify a conflicting jquery plugin is in javascript itself, where you can see if the object exists. But unfortunately I don't know of a way to have javascript code conditionally include other javascript code that's stored in another file.
Shotster replied on at Permalink Reply
Shotster
> But actually the technique in that thread wouldn't solve the issue I bring up here...

I was addressing only your question about how to include JS, but I think Travis has offered the best solution. I was unaware of that function as well. Thanks Travis!


> what do you do if there's another block or package that's not under your control (or is in the theme itself)?

That's where a dependency system would come in. In my view, all these client-side shenanigans are just kludgy work-arounds.


> you don't have any way of uniquely identifying a jquery plugin (for example) by its filename...

It wouldn't have to be by file name. I was thinking of a mechanism that was well thought out and tightly integrated into the CMS. Maybe the add-on or theme developer would have an official (and well-documented) way of specifying dependencies for their package. Ideally, these dependencies would be resolved at install time - not on the fly during page generation/rendering.

This issue was discussed recently (can't find the thread), and Andrew indicated a dependency system might come about some day, but I'm not holding my breath.

-Steve
TravisN replied on at Permalink Best Answer Reply
TravisN
You will want to use jQuery's getScript() function

You can still do your conditional test and then if it fails, load the plugin with getScript(). It also has a handy callback so that you can then apply the script to your element once loaded.

Below is in crude programming english, but you should get the idea.

// condition test in document.ready()
if (object loaded)
    myfunction();
else
    $.getScript('path_to_plugin.js', function () { myfunction() });
// end conditional test
//
//
// apply the plugin inside it's own function to avoid duplication of code.
function myfunction () {
   $('your_element').your_plugin_function();
}
jordanlev replied on at Permalink Reply
jordanlev
Wow, that's perfect -- I had no idea that function existed (obviously). Thanks!
agedman replied on at Permalink Reply
agedman
So if I'm understanding this correctly, wouldn't it be a best practice for theme-makers and block-builders to always use this function for loading their plugins?
ijessup replied on at Permalink Reply
ijessup
If I am not mistaken, the controller->addHeaderItem('') function will make sure a single file isn't loaded multiple times.

Example:
$html = Loader::helper('html');
$this->controller->addHeaderItem($html->javascript('file_name.js', 'package_handle'));
This will load the file packages/package_handle/js/file_name.js only once, even if you have the same code multiple times.
jordanlev replied on at Permalink Reply
jordanlev
@agedman - I'm not sure it's a best practice, because it seems kind of icky to include javascript this way as a normal matter of course -- I was just looking for a workaround for one specific situation on a site I'm working on (the javascript is actually included by the theme and the block, so I'd change the theme code to use this conditional thing). One problem with including js this way is that it doesn't load until AFTER the page has finished loading, so any code in your $(document).ready() function will not have access to this loaded js. I'm sure there's lots of other little problems with this that would keep it from being a generally recommended practice.

@ijessup -- the addHeaderItem function only "deduplicates" files loaded from the same exact location -- I'm referring to a situation where two different blocks (or the theme header directly) include the same jquery plugin. As mentioned in an earlier comment, even if addHeaderItem did "deduplicate" based on file name (regardless of which block it resides in), you would still have problems because, for example, a jquery plugin file could be called "lightbox.js" or "jquery.lightbox.js" or "jquery.lightbox.1.3.2.min.js", etc.
Shotster replied on at Permalink Reply
Shotster
Jordan,

How are you checking for the existence of the plugin? Assuming you're using something like...

if (jQuery.fn.lightbox) {
   // It's loaded.
} else {
   // It's not.
}

...then it seems agedman might have a good suggestion. I mean, why not adopt this practice as a way of avoiding duplicates? Of course, it doesn't address the issue of plug-in versions, but it's better than nothing.

Also, you say the code won't execute until after the page has loaded. It's not clear to me why that's necessarily the case. Why not put it in a DOM ready callback?

In my case (in the thread I referenced above), my JS code is not a jQuery plug-in, so I basically look at the document's "head" element to see if the script tag (referencing the script) is already there. If not, I append a script element to the document head. Were it a jQuery plug-in, however, I could simply check to see if the plug-in was loaded.

-Steve
jordanlev replied on at Permalink Reply
jordanlev
It DOES address the issue of plugin version, because regardless of version, the plugin will have the same name in javascript-land. And if it has a different name then there's no problem because there won't be a conflict then!

And you can't guarantee that the code will run when you want because the jQuery.getScript function makes an asynchronous call (it's just a wrapper function for an ajax call), so the rest of your javascript is running in the browser while the additional file is being retrieved. At least this is what I gather from reading the comments in the jQuery documentation:
http://api.jquery.com/jQuery.getScript/#comment-45819535...

-Jordan
Shotster replied on at Permalink Reply
Shotster
>It DOES address the issue of plugin version, because
> regardless of version, the plugin will have the same
> name in javascript-land.

But that IS the problem from my perspective. You might want to ensure you're using the latest version, or you might want functionality that's in a specific version.

As for the asynchronous loading issue, I can see where one block can be checking to see of a plug-in is loaded WHILE another is loading the file. That might result in a duplicate. It seems a solution would simply be to load the script synchronously.

-Steve
jordanlev replied on at Permalink Reply
jordanlev
I see your point. It's not the specific problem I was having, but you're right that it could be an issue in some situations.

This is probably like the standards-compliant HTML issues from that other recent thread -- it would be great in theory but would require massive infrastructure upheaval, and I don't really see anyone else here besides me and you running into or caring about these kinds of problems ;)
Shotster replied on at Permalink Reply
Shotster
> I don't really see anyone else here besides me
> and you running into or caring about these
> kinds of problems

I'm not willing to assume no one else cares or that nobody else has encountered this issue just because there aren't lots of posts about it. (It has actually come up before, but I can't find the thread.) Besides, as C5 grows and garners more developers, these types of scenarios certainly aren't going to become LESS frequent.

:-)

-Steve
jordanlev replied on at Permalink Reply
jordanlev
Yeah, I was just joking around -- I totally agree with you.

Although I also understand that there's no easy fix so I'm not holding my breath either.
ijessup replied on at Permalink Reply
ijessup
Best practice would be to use one file, placed at the root or package level rather than in a theme or in a block template.

So if you have a block, template and theme, package them all together.

A problem you might run into is when using multiple blocks/templates/themes from different people that may take advantage of the same plug-in. That's when TravisN's solution will come in handy. Though this issue would seem very unlikely, or at the very least questionable as to why.

It would be nice if the addHeaderItem checked for duplicate functions. I'm sure not a simple task, but would be nice.

But, again... if it is a commonly used JS, put it in a common area. If it is common only to your mods, put it at the package level, other wise, put it at the root level and mod the other packages.
jordanlev replied on at Permalink Reply
jordanlev
Yes, this isn't a problem at all if you're talking about everything within 1 package -- but I'm talking about a situation where I have a block and I'm worried that the end-user may have another block (or an inclusion in their theme's header element -- which is actually what happened to me on a client site which is why I asked this question in the first place).

You're correct, though, that it seems quite unlikely to happen "out in the wild" -- I was just looking for a solution for this 1 client site, where I can code up the theme to conditionally include the plugin IF it's not already loaded by a block that they may or may not have placed on any particular page in the site.

As for having addHeaderItem check for duplicate functions, I don't think this is possible because that function just loads files, and does not look inside the files to see what's there (and even if it could do that, you're talking about parsing javascript files on the server which seems like massive overkill for a problem of this small magnitude).

-Jordan
Shotster replied on at Permalink Reply
Shotster
Drupal has a plug-in registry which it seems might address these issues...

http://drupal.org/project/jq

Perhaps something similar is needed in C5?

-Steve
jbx replied on at Permalink Reply
jbx
Personally, I'd really like to see the calls to popular js files like jQuery moved out to a CDN. That way we could all use addHeaderItem and know that we are only including the file once. It would also solve the different filename issue, because if we were all using the Google CDN, the naming would be consistent.

**Although I note that this wouldn't solve the different version issue.

This way, the only js files that would be included from within a block that aren't hosted on a CDN would be our own unique js libraries, that really shouldn't be duplicated.

I guess we would still run into some issues if we release multiple blocks using a specific library we have written. Maybe the package controller / block installer could be modified so that we can install js file to the root js folder, outside of the package, so the files are always in the same location... Just thinking outloud here...

Jon
jordanlev replied on at Permalink Reply
jordanlev
This is a really interesting idea, although I'm not sure it would be prudent to force everyone to use it by having it in the core -- seems that for every person who claims speed gains, another person claims that it sporadically fails to load (for example, see this hacker news thread from the other day:http://news.ycombinator.com/item?id=1713685... ).

A single directory for js installs is also an interesting idea -- but would mean that installable packages aren't self-contained in their own directory.

Not saying that those are bad ideas -- just hashing though the potential issues.

Thanks for the great comment!

-Jordan
Shotster replied on at Permalink Reply
Shotster
Hi Jon,

I've been using Google's CDN for a while now, and I think it's great. There are a number of benefits - including faster load times for visitors - especially if the file being referenced is already in their cache (more likely the more developers using the CDN).

That said, I still develop with the local copy of jQuery, as a CDN isn't of much use (obviously) if you're not connected to the Net (or happen to be using a painfully slow connection). Additionally, I had some issues with spurious jQuery dialogs appearing while in edit mode due to a conflict with C5's dialog functionality, so I still have to serve jQuery UI from my hosting account until such a time as that issue is resolved.

A single directory isn't a bad idea, but it seems there should be some means of registering libraries (and versions) used by a package. It's not just the libraries themselves at issue here, though. There are a number of popular plug-ins, and it doesn't make sense to load multiple copies of the same plug-in if more than one add-on uses it - hence the need for a registry of sorts.

-Steve
JohntheFish replied on at Permalink Reply
JohntheFish
I just came across this thread while looking for solutions to my own jQuery and jQuery plugin version and loading issues. (I would be interested in what the schedule is for Concrete5 being updated to jQuery 1.5.2 and UI 1.8.11)

After using CDN for the jQuery, jQueryUI and jQueryUI themes, another advantage of a central script repository is that all other scripts can then be concatenated and compressed into a single file, reducing the number of separate files that need to be loaded. If this file is consolidated across a whole site, then JavaScript load times come way down after a browser has cached it.

Once established, the repository/concatenate/compress could also be used for css.

Or am I missing something - have I failed to find a plugin that already does all this for me?
jordanlev replied on at Permalink Reply
jordanlev
http://www.concrete5.org/community/forums/chat/concrete-on-steroids/