Dynamically include JavaScript modules - javascript

I am trying to dynamically load (aka 'include') a series of additional .js "module" files from a parent script.
I am doing this as part of a D3 reusable charts library which, when I publish, I actually run a Makefile which concatenates said "modules" together into a single script (and therefore removes the need for the dynamic include method). But while testing I want to load these "modules" separately (mainly to save me having to run the Makefile script each time I make a small change to one of the files).
I have figured out 3 possible solutions, and written different 'include' functions, one for each solution:
https://github.com/jamesleesaunders/d3.ez/blob/master/js/d3.ez.js
// Solution 1 - Using document.write
function includeV1(file) {
var loc = document.currentScript.src
var path = loc.substring(0, loc.lastIndexOf("/") + 1);
var src = path + file;
var type = 'text/javascript';
document.write('<' + 'script src="' + src + '"' + ' type="' + type + '"><' + '/script>');
}
// Solution 2 - Using document.createElement
function includeV2(file) {
// Method 2 - Using document.createElement
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
var loc = document.currentScript.src
var path = loc.substring(0, loc.lastIndexOf("/") + 1);
script.src = path + file;
script.type = 'text/javascript';
head.appendChild(script);
}
// Solution 3 - Using D3
function includeV3(file) {
var loc = document.currentScript.src
var path = loc.substring(0, loc.lastIndexOf("/") + 1);
var src = path + file;
var type = 'text/javascript';
d3.select('head')
.append("script")
.attr('src', src)
.attr('type', type);
}
Called like:
include('header.js');
include('radialBarChart.js');
include('circularHeatChart.js');
include('discreteBarChart.js');
Solution 1 above seems to work fine, and does exactly what I want to, however Solution 2 and 3 do not seem to work? Even though, as far as I can tell, they are doing the same thing.
I think the possible difference is that Solutions 2 and 3 insert the additional elements to the DOM after the page has loaded (and therefore not available to the main page script when it calls for one of the module functions). Whereas I am guessing that Solution 1 adds the tags at the point where it is called (and therefore module functions are available to the main page script when called for).
I am happy if Solution 1 is the only solution, as it works, but to satisfy my curiosity and also because I am writing a D3 library I would have preferred to have used Solution 3.
Can anyone advise why Solutions 2 and 3 do not work? Or offer any amendments?
I am not looking for a jQuery solution, but there may be some jQuery experts who may know?
For reference here is the Makefile which, when publishing, I run to concatenate the "module" files into a single script:
https://github.com/jamesleesaunders/d3.ez/blob/master/Makefile
Which then generates the concatenated:
https://github.com/jamesleesaunders/d3.ez/blob/master/d3.ez.js

This has now been resolved by moving to use of ES6 modules.

Related

How can I add the URL's root path to the paths of my JS files?

So, I have a web app that runs at localhost:XXXXX on my machine (where XXXXX is a port number), but in production it runs at webappurl.com/portal. The problem is that when I use relative paths for my JS files it thinks the root URL is just / and not /portal.
Is there a way for me to maybe check the root path, say using location.pathname, and then add that to the beginning of each of my JS file paths?
For example, take this
<script type="text/javascript" src="/Scripts/example.js"></script>
and change it to this
<script type="text/javascript" src="/portal/Scripts/example.js"></script>
I'd like to stay away from hard coding the root path, mainly because that's not best practices, but also because the root path is different in my dev environment and I don't want to have to constantly change the paths back and forth or to have to keep to separate files.
While the 'base' element seems like it may be the answer, the problem is we are dealing with a case where the base is going to move around depending on the deployment environment. One solution is to boot-strap the script loading after the environment has been discovered. I use a similar approach in production:
<script>
(function (){
var
// All the JS files we want to load
script_list = [ 'foo.js', 'bar.js', 'bang.js' ],
// Our JS directory
script_base = '/js/',
doc_path = document.location.pathname,
base_list = doc_path.split('/'),
append_script_tag, base_path, i;
// pop-off the document name from the location to
// get the directory only
base_list.pop();
base_path = base_list.join('/');
// a handy script tag appender function
append_script_tag = function ( script_url ) {
var script_obj = document.createElement( 'script' );
script_obj.src = script_url;
script_obj.charset = 'utf8';
script_obj.type = 'text/javascript';
document.body.appendChild( script_obj );
};
// now loop through the list of js files and write the script tags
for ( i = 0; i < script_list.length; i++ ) {
append_script_tag( base_path + script_base + script_list[ i ] );
}
}());
</script>
Boom, you're done :)
Of course, relative paths are always better if you can swing it. But sometimes, you can't. For example, sometimes one uses local files for testing but a CDN in production. You might load different libraries depending upon the environment, and this approach is one way to determine the environment in use.
Cheers, and good luck!
Check out the base tag.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
It enables you to specify the base url for all of your relative file references.
Methinks that it will prove to be very helpful for you ;)
Pathes with leading slashes are not relative but absolute. The / just points to the application's or site's root directory. You can modify this by using the base tag, see kmiklas answer for further information.
In your case it might be sufficient to just omit the leading slash, though.

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)

HTML templates inside JavaScript file... What am I doing wrong?

What I'm trying to achieve is to store HTML templates for everything that needs to be generated on the fly inside a separate js file (instead of rendering it in the page).
The buildHtml function how its currently setup works fine. Where I'm stuck is what if.. I've another variable inside the template object say 'input3' and its markup is something like <div>(exact markup from commonInput)</div>
I tried using it as input 3 : '<div>' + this.commonInput + '</div>' but it turns out you cannot refer object properties from inside using this (explained here).
I could create 'input3' with full html but for big html chunks this approach is not very useful.
Looking for
solution to this specific problem
reasons if this approach is a bad idea
better alternatives
$j(document).ready(function() {
var namespace = window.namespace || {};
namespace.feature = namespace.appName || {};
namespace.feature.templates = {
input1 : '<div>'+
'<p class="abc">Hey {username}</p>'+
'</div>',
input2 : '<div>'+
'<div class="description">{description}</div>'+
'</div>',
commonInput : '<div class="common">Common code</div>'
};
namespace.feature.module = function() {
var container = $j('#container'),
t = namespace.feature.templates;
var buildHtml = function(type) {
var tmpHtml = t.input1 + t.commonInput + t.input2;
container.append(tmpHtml);
}
var init = function() {
buildHtml();
}
return {
init : init,
};
}();
namespace.feature.module.init();
});
just on the top of my head.
You could write the templates as functions that build strings.
input3 : function(param) {
return '<div>' + param + '</div>'
}
then:
var tmpHTML = t.input1 + t.input2 + t.input3(t.commonInput);
Also, i like to build my own DOM objects when building HTML. and avoid the use of hardcoded strings.
http://mg.to/2006/02/27/easy-dom-creation-for-jquery-and-prototype
I'm not sure what your exact question is, but as far as better alternatives go,
I would suggest using jQuery templates.
Here is a benchmarking page for all the different templating engines and their performance:http://jsperf.com/dom-vs-innerhtml-based-templating
You can look at the revisions to find different combinations of engines as well as run them on different browsers to see speed differences.
Update: The original jquery template project is no longer active. This is the new home for the old project: https://github.com/BorisMoore/jquery-tmpl
I would no longer recommend jquery-templates since there are much better alternatives now. The last time I checked, dustJs by linkedIn fork seems to be the most promising one.

Variable Scope Issue - Referencing a Variable in External JS File

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.

Categories

Resources