Variable Scope Issue - Referencing a Variable in External JS File - javascript

I believe the issue below relates to variable scope, although I may be mistaken.
I've created a javascript bookmarklet that inserts a few external javascript files into the web page. For this bookmarklet, I need to insert 3 external js files. To insert these files, I'm using about 6 different functions, all that are very similar. I tried to create a new function to consolidate these functions, but it keeps failing. I believe it fails because of a variable scope issue, but I may be mistaken.
Here is the code that works to insert one (of 3) external js files:
jQueryCheck();
function jQueryLoader() {
var jQ = document.createElement('script');
jQ.type = 'text/javascript';
jQ.onload = jQueryCheck;
jQ.src = 'http://example.com/jquery.js';
document.body.appendChild(jQ); //the jQuery variable get defined here
}
function jQueryCheck() {
if (typeof jQuery == 'undefined') {
jQueryLoader();
} else {
tableSorterLoader();
}
}
The above code works, but I have to run nearly identical functions 3 times in order to insert 3 separate external files. I tried to consolidate the code into the following (which fails):
var scripts = [];
scripts[0] = 'http://example.com/jquery.js';
scripts[1] = 'http://example.com/plugin2.js';
scripts[2] = 'http://example.com/plugin3.js';
jsLoader(scripts, mainFunction);
function jsLoader(file,nextFunction) {//nextFunction is the function that runs after jsLoader is finished
for (var i = 0; i <= scripts.length; i++) {
function insertScript() {
var js = document.createElement('script');
js.type = 'text/javascript';
js.onload = scriptCheck;
js.src = file[i];
document.body.appendChild(js);//the jQuery variable fails to get defined here
}
function scriptCheck() {
var variableTest = (typeof jQuery);
if (typeof jQuery == 'undefined') {
insertScript();
}
}
scriptCheck();
}
nextFunction();
}
I believe I isolated where the problem occurs: after document.body.appendChild(js); (see the comments). In the first set of functions, the jQuery variable is successfully defined at this line of code (because the jQuery library is inserted). However, in the second function, the jQuery variable is not getting defined even though the jQuery library still is being successfully inserted into the web page's html. It is necessary to validate whether jQuery has been defined so that the remainder of the code does not execute until jQuery is active and available.
Can anyone suggest some causes to this problem as well as the solutions? Additionally, can anyone suggest improvements to my new function jsLoader() to make it more intelligent/professional?

It looks like you need the jquery file loaded before the plugins will work, so I would not try to load all three at once (like the second code is trying), but to approach it probably like what the initial 3 function calls did (maybe).
As the comments suggest, I would not use a loop (unless you have many plugin files), but rather use an un-nested loading function like jsLoader. The first call loads jQuery with nextFunction calling jsLoader for the first plugin file with it's nextFunction calling the last file. Then you know when you files are all loaded.

Related

Avoiding jQuery conflicts in a dynamically loaded JavaScript application

I am currently trying to develop a JavaScript application that can be embedded in an existing webpage (which I cannot modify). The application needs a specific version of jQuery.
The script used for loading the application is doing the following:
// loading JavaScript needed by my application
(function () {
document.write('<script src="../jquery-1.10.2.min.js"></script>');
document.write(...); // load other scripts (using jQuery 1.10.2)
// storing the application's jQuery in a new namespace
// to avoid conflicts with other jQuery versions
document.write('<script type="text/javascript">' +
'appJQ = jQuery.noConflict(true);</script>'); // error: jQuery is undefined in IE<10
})();
// initializing the application itself
document.onload = function() {
// ...
};
This works fine in every browser I've tested, except IE < 10. In IE 9 and lower I am getting the error that jQuery is undefined.
Moving jQuery to a new namespace in the document.onload function would work for my application but causes conflicts with other scripts on the webpage that includes my application if they need a different version of jQuery.
Do you have any suggestions how to solve this problem?
Thanks for your help!
Instead of using document.write, try creating a <script> element and defining an onload handler for that element:
(function () {
var script = document.createElement('script');
script.src = '//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js';
script.onload = function() {
var appJQ = jQuery.noConflict(true);
// app initialization code
};
var head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(script);
})();
If you have multiple scripts that depend on one another, you might want to try using a script loader such as HeadJS or LABjs.
If you want even greater flexibility in managing dependencies, you can try using a module loader such as RequireJS, Browserify, or webpack.
Following Hamza's answer, you can use this method of loading in a different way:
var s = document.createElement("script");
s.src = "../jquery.min.js" // insert your own jQuery link
s.onload = function() {
var appJS = jQuery.noConflict(true);
}
Hope this helps.
The document.write calls would also happen after the document is considered loaded, in which case your onload function might fire before the contents of that first function. You want to load the javascript files in a better way, such that their onload initiates the application itself.

How to get name of JavaScript file being executed?

I'm working on a project that contains several features, so I'm trying to organize each feature on a separated JavaScript file in such a way that the code can be loaded on demand. I want to standardize the load and execution process so I'm trying to develop a function that can load and execute the JavaScript file if it is not present in memory or just execute it if it is already loaded. One approach would be to make the function to require two parameters, the file name and the name of entry point function, then check if the script is already loaded, if it is just execute the entry point function, or if it is not, then load the script and upon the onload event execute the entry point function, this approach works fine but it requires the main module to know all the entry point function names, which in the future may become a pain in the neck, as a careless programmer may inadvertently duplicate a function name. A much cleaner and safer approach would be to name "main" all the entry point functions, and upon first execution associate that "main" to its script DOM object... more or less like this:
Loaded module:
function(){
function main(){
.
. some code here
.
}
Install(getThisFileName(),main);
}();
Main module:
function Load(fn){
var el,ff,i
el = document.getElementsByTagName("script");
ff = "http://"+window.location.host+fn;
for(i=el.length-1;i>=0;i--){
if(el[i] && el[i].src==ff){
el[i].Main();
return;
}
}
el = document.createElement("script");
el.type = "text/javascript";
el.src = ff;
}
function Install(ff,ep){
var el,i;
el = document.getElementsByTagName("script");
for(i=el.length-1;i>=0;i--){
if(el[i] && el[i].src==ff){
el[i].Main = ep;
ep();
return;
}
}
}
But in order to do so I need to know the name of the JavaScript file being executed, which I don't know if it is even possible. Any Ideas?
The easiest way might be to have included first:
var currentFile;
function getThisFileName() {
return currentFile;
}
And at the beginning of each file at the top simply do:
currentFile = 'thisFileName.js';
This will only work however if your code is executed sequentially, once all the files are loaded currentFile will always be the name of the last file.
And alternative method would be to set currentFile at the start of each function in each file. So while that specific function is being accessed you can call your getThisFileName() and it will tell you the last file run, or the current file being run.
Unfortunately this has a bit of overhead with your code itself.
The following will give you all the scripts as an array.
var allScripts = document.getElementsByTagName("script");
The current file will always be the last one .. so allScripts[allScripts.length-1] will give you the current javascript file. You can access the src attribute from this.
EDIT:
This won't work for asynchronous implementation.
You could globally declare an object like:
var scripts = {
addScript : function(filename) {
scripts[filename] = {
loaded : true,
executed : false
}
}
}
Then at the very bottom of each file call:
scripts.addScript('myFilename.js');
Then at the end of the script's execution call:
scripts['myFilename.js'].executed = true;
Then at any time you can check scripts to see if a particular file is loaded/executed.

JS files out of order

I'm having a very frustrating problem with my javascript files. I have one global namespace that contains a page_data, utilities, modules etc namespace.
My directories look like this:
/utilities/single_utility.js
/modules/module.js etc tec
Anyways, I load the utilities before the modules, (which use the utilities) but keep getting problems.
My loader script looks like this (its wrapped in an SEAF):
for (var index in file_list) {
var url = file_list[index];
var new_script = document.createElement('script');
new_script.setAttribute("type", "text/javascript");
new_script.setAttribute("src", url);
element.appendChild(new_script);
}
Project is my global namespace, that holds all of these other namespaces. But when I try to reference a utility in one of my modules, with Project.utilities.session.exist() etc, it will sometimes throw an error that Project can't be found?
How can I fix this to make sure my files are loading properly or am I doing something else wrong?
Using async = false should protect your load order.
This is a quick snippet I use to load and maintain order.
var loadScript = function( url ) {
if (url) {
var doc = document,
t = doc.createElement("script"),
s = doc.getElementsByTagName("script")[0];
t.type = "text/javascript";
// Keep script order!
t.async = false;
t.src = url;
s.parentNode.insertBefore(t, s);
}
};
Some references backing this logic (and regarding browser support) from MDN and Microsoft
When you add the script tag to the page, the browser has to go out and download those scripts. There's no guarantee that they will download and execute in the same order that you created them.
You will need to add an event listener to new_script for when it loads, either onload or onreadystatechange, depending on the browser. But truly I'd recommend using a script loading library (such as RequireJS) that handles all the nasty details for you.
As an aside, why not just add all your script tags directly to your page? In that case they load sequentially.
It's better if you resolve this problem using a script loader.
As Matt said RequireJS is a good option if you also want to handle script dependencies.
For script loading only, you can take a look into LAB.js, is very small and straightforward:
for (var index in file_list) {
$LAB.script(file_list[index]);
}
If the scripts have dependencies and must be loaded in order, you can use .wait(), to do that keep the chain object returned by script. You can re-write the loop to keep the chain:
var chain;
for (var index in file_list) {
if (!chain) {
chain = $LAB.script(file_list[index]).wait();
} else {
chain.script(file_list[index]).wait();
}
}
Or just forget the file_list array and use $LAB directly:
$LAB.script('file1.js').wait()
.script('file2.js').wait()
/* ... */

Auto-load/include for JavaScript

I have file called common.js and it's included in each page of my site using <script />.
It will grow fast as my sites functionality will grow (I hope; I imagine). :)
Lets example I have a jQuery event:
$('#that').click(function() {
one_of_many_functions($(this));
}
For the moment, I have that one_of_many_functions() in common.js.
Is it somehow possible that JavaScript automatically loads file one_of_many_functions.js when such function is called, but it doesn't exist? Like auto-loader. :)
The second option I see is to do something like:
$('#that').click(function() {
include('one_of_many_functions');
one_of_many_functions($(this));
}
That not so automatically, but still - includes wanted file.
Is any of this possible? Thanks in an advice! :)
It is not possible to directly auto-load external javascripts on demand. It is, however, possible to implement a dynamic inclusion mechanism similar to the second route you mentioned.
There are some challenges though. When you "include" a new external script, you aren't going to be able to immediately use the included functionality, you'll have to wait until the script loads. This means that you'll have to fragment your code somewhat, which means that you'll have to make some decisions about what should just be included in the core vs. what can be included on demand.
You'll need to set up a central object that keeps track of which assets are already loaded. Here's a quick mockup of that:
var assets = {
assets: {},
include: function (asset_name, callback) {
if (typeof callback != 'function')
callback = function () { return false; };
if (typeof this.assets[asset_name] != 'undefined' )
return callback();
var html_doc = document.getElementsByTagName('head')[0];
var st = document.createElement('script');
st.setAttribute('language', 'javascript');
st.setAttribute('type', 'text/javascript');
st.setAttribute('src', asset_name);
st.onload = function () { assets._script_loaded(asset_name, callback); };
html_doc.appendChild(st);
},
_script_loaded: function (asset_name, callback) {
this.assets[asset_name] = true;
callback();
}
};
assets.inlude('myfile.js', function () {
/* do stuff that depends on myfile.js */
});
Sure it's possible -- but this can become painful to manage. In order to implement something like this, you're going to have to maintain an index of functions and their corresponding source file. As your project grows, this can be troublesome for a few reasons -- the 2 that stick out in my mind are:
A) You have the added responsibility of maintaining your index object/lookup mechanism so that your scripts know where to look when the function you're calling cannot be found.
B) This is one more thing that can go wrong when debugging your growing project.
I'm sure that someone else will mention this by the time I'm finished writing this, but your time would probably be better spent figuring out how to combine all of your code into a single .js file. The benefits to doing so are well-documented.
I have created something close to that a year ago. In fact, I have found this thread by search if that is something new on the field. You can see what I have created here: https://github.com/thiagomata/CanvasBox/blob/master/src/main/New.js
My project are, almost 100% OOP. So, I used this fact to focus my solution. I create this "Class" with the name "New" what is used to, first load and after instance the objects.
Here a example of someone using it:
var objSquare = New.Square(); // Square is loaded and after that instance is created
objSquare.x = objBox.width / 2;
objSquare.y = objBox.height / 2;
var objSomeExample = New.Stuff("some parameters can be sent too");
In this version I am not using some json with all js file position. The mapping is hardcore as you can see here:
New.prototype.arrMap = {
CanvasBox: "" + window.MAIN_PATH + "CanvasBox",
CanvasBoxBehavior: "" + window.MAIN_PATH + "CanvasBoxBehavior",
CanvasBoxButton: "" + window.MAIN_PATH + "CanvasBoxButton",
// (...)
};
But make this more automatic, using gulp or grunt is something what I am thinking to do, and it is not that hard.
This solution was created to be used into the project. So, the code may need some changes to be able to be used into any project. But may be a start.
Hope this helps.
As I said before, this still is a working progress. But I have created a more independent module what use gulp to keep it updated.
All the magic que be found in this links:
https://github.com/thiagomata/CanvasBox/blob/master/src/coffee/main/Instance.coffee
https://github.com/thiagomata/CanvasBox/blob/master/src/node/scripts.js
https://github.com/thiagomata/CanvasBox/blob/master/gulpfile.js
A special look should be in this lines of the Instance.coffee
###
# Create an instance of the object passing the argument
###
instaceObject = (->
ClassElement = (args) ->
window[args["0"]].apply this, args["1"]
->
ClassElement:: = (window[arguments["0"]])::
objElement = new ClassElement(arguments)
return objElement
)()
This lines allows me to initialize a instance of some object after load its file. As is used in the create method:
create:()->
#load()
return instaceObject(#packageName, arguments)

Including a .js file within a .js file [duplicate]

This question already has answers here:
How do I include a JavaScript file in another JavaScript file?
(70 answers)
Closed 9 years ago.
I'd like to know if it is possible to include a .js file within another .js file?
The reason for me wanting to do this is to keep client includes to a minimum. I have several .js files already written with functions that are needed by the client. The client would have an html file which he/she manages with a .js file include (my .js file).
I could re-write a new .js file with all the functions in it or, to avoid doing double work, figure out a way to write a .js file that includes other .js files.
I basically do like this, create new element and attach that to <head>
var x = document.createElement('script');
x.src = 'http://example.com/test.js';
document.getElementsByTagName("head")[0].appendChild(x);
You may also use onload event to each script you attach, but please test it out, I am not so sure it works cross-browser or not.
x.onload=callback_function;
The best solution for your browser load time would be to use a server side script to join them all together into one big .js file. Make sure to gzip/minify the final version. Single request - nice and compact.
Alternatively, you can use DOM to create a <script> tag and set the src property on it then append it to the <head>. If you need to wait for that functionality to load, you can make the rest of your javascript file be called from the load event on that script tag.
This function is based on the functionality of jQuery $.getScript()
function loadScript(src, f) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = src;
var done = false;
script.onload = script.onreadystatechange = function() {
// attach to both events for cross browser finish detection:
if ( !done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") ) {
done = true;
if (typeof f == 'function') f();
// cleans up a little memory:
script.onload = script.onreadystatechange = null;
head.removeChild(script);
}
};
head.appendChild(script);
}
// example:
loadScript('/some-other-script.js', function() {
alert('finished loading');
finishSetup();
});
There is no straight forward way of doing this.
What you can do is load the script on demand. (again uses something similar to what Ignacio mentioned,but much cleaner).
Check this link out for multiple ways of doing this:
http://ajaxpatterns.org/On-Demand_Javascript
My favorite is(not applicable always):
<script src="dojo.js" type="text/javascript">
dojo.require("dojo.aDojoPackage");
Google's closure also provides similar functionality.
A popular method to tackle the problem of reducing JavaScript references from HTML files is by using a concatenation tool like Sprockets, which preprocesses and concatenates JavaScript source files together.
Apart from reducing the number of references from the HTML files, this will also reduce the number of hits to the server.
You may then want to run the resulting concatenation through a minification tool like jsmin to have it minified.
I use #gnarf's method, though I fall back on document.writelning a <script> tag for IE<7 as I couldn't get DOM creation to work reliably in IE6 (and TBH didn't care enough to put much effort into it). The core of my code is:
if (horus.script.broken) {
document.writeln('<script type="text/javascript" src="'+script+'"></script>');
horus.script.loaded(script);
} else {
var s=document.createElement('script');
s.type='text/javascript';
s.src=script;
s.async=true;
if (horus.brokenDOM){
s.onreadystatechange=
function () {
if (this.readyState=='loaded' || this.readyState=='complete'){
horus.script.loaded(script);
}
}
}else{
s.onload=function () { horus.script.loaded(script) };
}
document.head.appendChild(s);
}
where horus.script.loaded() notes that the javascript file is loaded, and calls any pending uncalled routines (saved by autoloader code).

Categories

Resources