Including external javascript which doesn't honor closure usage - javascript

I have to include an external javascript file as part of a Drupal project I'm working on.
Unfortunately, this external file doesn't make correct use of closures, for example:
(function($) {
function test() {...}
})(jQuery);
function test2() {
console.log($('#xyz').val());
}
Which leads to a '$ is undefined' error whenever test2() is executed.
I know that Drupal makes sensible use of closures to allow harmonious coexistence of libraries, which is very useful, but in this case it'll be a nightmare to get the team responsible for said external JS file to make the necessary changes (and will also involve trying to explain to the "experts" responsible).
Is there any way that I can give this external file access to use $ as a pointer to jQuery?
Ideally I'd like some solution which doesn't involve a huge workaround at my end, but can still make use of the external version.
Failing that, I'll just take a local copy, and fix accordingly.
Thanks!

Just assign a global.
window.$ = jQuery

You will either need to take a copy and modify it to suit, or use PHP to grab the file and output it, for example if you pointed the javascript src to a local php file, in that php file you could do something like
$js = file_get_contents("externaljsfile");
echo ";(function($) {";
echo $js;
echo "})(jQuery);";
There is a way i heard of, of using imported javascript which may possibly work (javascript macro's?), however i've never read up on them (if they even exist) and they wouldn;t be supported by enough browsers to be a viable solution anyway

Related

How/when/where to include external javascript

I'm looking for some advice on the best way to hold my JavaScript (jQuery) functions.
I am developing in MVC/razor and therefore have a layout page. I include my jQuery library and an external JavaScript file in here so it's available in every single page.
This is working well, but I am now becoming very aware of the fact that I am adding almost 300 lines of JS to EVERY page, where maybe half of that is used in any one of these pages.
One function is not in the external file and instead sits inside the HTML because I need to use variables set in my razor code.
I have a couple of questions around this arrangement:
Is placing JS inside the HTML generally acceptable when variables set using razor are used? There does not appear to be a clean way of passing a variable into an external js file
Should I split my functions down in to individual JS files and just include what is needed for each page in the site?
If I were to split them into multiple files, how would that work with jQuery's (document).ready ? Do I need to use that if all the JavaScript I am including is to be used?
I'm sure this will more a matter of opinion than a black and white answer, but I want to consider all my options before moving on. Even though it works fine as is, I can't help but feel there is a better/cleaner way.
Remember once a user lands on your homepage and loads the javascript file it will be cached in their browser so subsequent pages will not download the Javascript again.
I would definitely keep the js separate, you could have a snippet on each page that initialise the JS that that particurlar view needs. Put something like the below in the views that need to run JS
$(document).ready(function() {
mysite.mypage();
});
Then the function mysite.mypage() can be defined in the external JS file.
300 lines isnt the end of the world, I would say its probably too early to be worryign about optimisation.
You could always look at minifying that JS file to decrease the size. A quick and easy way to do this is here:
http://www.minifyjavascript.com/
Have you ever heard of require.js? http://requirejs.org/ I find it really useful.
It's a module loader so you are able to split all of your JS code into individual files and load only the ones you need on each page.
I don't know about passing a variable to an external JS file, I don't think its possible / the 'right' way.
You can make each external JS file into a function that accepts and returns parameters. Then in the page you need to use it:
- include the file dependancy
- call the function
Thats what I do, seems like your 2nd suggestion.
for the $(document.ready) question its really up to you. You don't have to use it but its useful for some things , check out this overview:
http://docs.jquery.com/Tutorials:Introducing_$(document).ready()

Javascript .js File Guidelines - How do I use a function outside of this file?

So two part question here. Basically, what is the proper practise for javascript function locations? I assumed it would be to have several MyScriptFile.js files, each with a few functions instead of one huge AllMyScripts.js file, as not every page needs every function.
However I'm not sure how to reference another function from outside of this file...
My situation: I'm using an AJAX request in many of my pages. Each request response is different (drawn from different files, etc) and is very hard to make dynamic (one-script-fits-all would be difficult). However, I do have my MakeAJAXRequest() function which creates the request object, which is standard to all DoSomethingWithRequest() functions.
How do I include MakeAJAXRequest() in the other files which contain the DoSomethingWithRequest() functions? It seems as though I should have been able to find this.. but I havn't come across it.
tl;dr I have MakeObject.js and UseObject.js. How does UseObject() reference MakeObject()?
EDIT: Found that if you include MakeObject BEFORE UseObject in your HTML <script> tags, UseObject will be able to reference MakeObject. Seems a little dirty still, as anybody who wants to use the UseObject script will have to be aware of the MakeObject dependency...
If you want to ensure your dependencies are loaded, you could use a function such as this: http://phpjs.org/functions/include:433 There is also include_once(), require(), and require_once()

Adding a JavaScript file

I'm trying to insert reference to the Javascript file in the header by using drupal_add_js(). I placed this line inside the template preprocess function in template.php. The result that the code is not working at all: There is no script link in output as it should be. Can anyone tell me what am I doing wrong?
function phptemplate_preprocess_page(&$vars) {
$url = drupal_get_path("theme","mysite");
drupal_add_js($url."/jquery.js");
drupal_add_js($url."/drupal.js");
.....
Even easier, Javascript that needs to be loaded on all pages can be added in the theme's .info file. See http://drupal.org/node/171205#scripts.
drupal_add_js(path_to_theme().'/js/jquery.cycle.all.js');
$vars['scripts'] = drupal_get_js();
If you place the javascript file in the theme directory, you can just add the following to the themes .info file
scripts[] = myJavaScriptFile.js
After you add this file you need to deactivate your theme and then reactive it.
As pointed by other, simply using drupal_add_js() from a hook_preprocess_page() implementation doesn't work. The references to JavaScript files collected through the multiple calls to drupal_add_js() are used to generate the corresponding markup into the $scripts variables from template_preprocess_page(). But a theme's implementation of hook_preprocess_page() is always called after template_preprocess_page(). So in order to have the files added through drupal_add_js() in your .tpl.php file(s), you need to override the already set $scripts variables:
function THEME_preprocess_page(&$variables)
drupal_add_js(...);
$variables['scripts'] = drupal_get_js();
}
But, you shouldn't have to add jquery.js and drupal.js yourself, it should already be done automatically by Drupal core. If you need to do it yourself, then something is broken on your site. You can (re-)add the files as a quick fix, but you better find the root cause of the issue as it is most likely creating other issues you haven't yet identified (or worked around without realizing it).
drupal_add_js() works, but you are putting it deep into the page rendering process. I suggest you put it in the template.php like you are doing, but in the beginning, outside any function. This is what we did on a few of our projects.

Is there a good way to prevent jQuery from being redefined?

I encountered a problem that took me some time to debug where a plug-in that I was using for jQuery (in this case jFeed) was not working. The problem ended up being because we also used Amazon Associates product previews. The product previews code ends up including a number of other JS files through document.write(), including another copy of jQuery. Because the product previews code appeared below the jFeed script, jQuery was redefined without the getFeed function.
Is there a best practice to ensure that certain objects like jQuery only get defined once on a page? I'm thinking of something like #ifndef with C/C++, but I don't know how it would work in this case where I didn't write the code that dynamically pulled in jQuery again.
I think in your situation, it would probably be best to redefine the jQuery variable as something else. The other jQuery code might use a different version so you might want to define a new variable which would indicate which jQuery you're using.
You could so something like this:
<script>
var $jMain = jQuery;
</script>
You would then just use the $jMain instead of jQuery or $. It'll be up to you to you to ensure you have the correct jQuery object when you do this. Here's the documentation.
Unfortunately the environment inside one JS sandbox (like within a window or frame of a browser) was not really designed to support the modern world of pulling in scripts from various places; there's no way you can say "define this object and make it resistant to redefinition". (You can even redefine most of the Javascript built-ins if you try!)
Your best shot is to make sure that your code is eval'd last, which gives you final say over the state of the environment when it runs. That doesn't mean other code can't come along later and clobber your definitions, but that's generally really bad form. You can do this by having your script tag be the last element in the body of the document, for example.
See also this jQuery method, which won't help you directly, but gets you thinking about some solutions to page sharing: http://api.jquery.com/jQuery.noConflict/

How can I inject Javascript (including Prototype.js) in other sites without cluttering the global namespace?

I'm currently on a project that is a big site that uses the Prototype library, and there is already a humongous amount of Javascript code.
We're now working on a piece of code that will get "injected" into other people's sites (picture people adding a <script> tag in their sites) which will then run our code and add a bunch of DOM elements and functionality to their site. This will have new pieces of code, and will also reuse a lot of the code that we use on our main site.
The problem I have is that it's of course not cool to just add a <script> that will include Prototype in people's pages. If we do that in a page that's already using ANY framework, we're guaranteed to screw everything up.
jQuery gives us the option to "rename" the $ object, so it could handle this situation decently, except obviously for the fact that we're not using jQuery, so we'd have to migrate everything.
Right now i'm contemplating a number of ugly choices, and I'm not sure what's best...
Rewrite everything to use jQuery, with a renamed $ object everywhere.
Creating a "new" Prototype library with only the subset we'd be using in "injected" code, and renaming $ to something else. Then again I'd have to adapt the parts of my code that would be shared somehow.
Not using a library at all in injected code, to keep it as clean as possible, and rewriting the shared code to use no library at all. This would obviously degenerate into us creating our own frankenstein of a library, which is probably the worst case scenario ever.
I'm wondering what you guys think I could do, and also whether there's some magic option that would solve all my problems...
For example, do you think I could use something like Caja / Cajita to sandbox my own code and isolate it from the rest of the site, and have Prototype inside of there? Or am I completely missing the point with that?
I also read once about a technique for bookmarklets, were you add your code like this:
(function() { /* your code */ })();
And then your code is all inside your anonymous function and you haven't touched the global namespace at all.
Do you think I could make one file containing:
(function() {
/* Full Code of the Prototype file here */
/* All my code that will run in the "other" site */
InitializeStuff_CreateDOMElements_AttachEventHandlers();
})();
Would that work? Would it accomplish the objective of not cluttering the global namespace, and not killing the functionality on a site that uses jQuery, for example?
Or is Prototype too complex somehow to isolate it like that?
(NOTE: I think I know that that would create closures everywhere and that's slower, but I don't care too much about performance, my code is not doing anything that complex)
If you're injecting your code into other sites, either require that they also include prototype, or don't use a library at all.
Any other option is, in my opinion, far too intrusive.
Daniel,
I am running into the same issue for some of the same reasons. Thanksfully, I am able to use jquery. What I want to do is create a single js file that will load up an instance of jquery and any dependent libraries I need. Then I want to load up css in the same way.
I found a script that will load these files into the DOM, but they don't seem to work. Here is what I am using:
function loadjscssfile(filename, filetype){
if (filetype=="js"){ //if filename is a external JavaScript file
var fileref=document.createElement('script')
fileref.setAttribute("type","text/javascript")
fileref.setAttribute("src", filename)
}
else if (filetype=="css"){ //if filename is an external CSS file
var fileref=document.createElement("link")
fileref.setAttribute("rel", "stylesheet")
fileref.setAttribute("type", "text/css")
fileref.setAttribute("href", filename)
}
if (typeof fileref!="undefined")
document.getElementsByTagName("head")[0].appendChild(fileref)
}
I can see in firebug that the css is loaded, but the contents of my css file are not coming through. I had hoped that I could dynamically load the libraries I needed based on the tools that this WIDGET used. So I would like to do some programmatic logic that would determine which libraries and css files should be "imported".
Not sure if this line of thinking plays into your efforts, but I would love to get your thoughts, or see where you end up going with this.

Categories

Resources