I'm still learning by making my own loader; here's my progress:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
(function( $ ){
$.plugin = {
loadJS: function(src, onload, onerror){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = src;
script.onload = onload;
script.onerror = onerror;
script.onreadystatechange = function () {
var state = this.readyState;
if (state === 'loaded' || state === 'complete') {
script.onreadystatechange = null;
onload();
}
};
document.body.appendChild(script);
},
loader: function(o) {
var loaded = ({js:[],css:[]});
// http://www.crockford.com/javascript/private.html
var that = this;
var phase = 0;
$.each(o["js"], function(key,src) {
that.loadJS(src,
function(){
loaded['js'].push(src);
});
});
console.log(loaded['js'].length)
// IF JS ALL LOADED, this is the main problem
if (loaded['js'].length == o["js"].length) {
alert('problem solved')
that.loadJS(o["script"]);
};
}
};
})( jQuery );
</script>
</head>
<body>
<script>
$(document).ready(function() {
$.plugin.loader({
js: [
'1.js', // async
'2.js', // async
'3.js', // async
'4.js' // async
],
script: '5.js', // after js loaded
debug: 1
});
});
</script>
</body>
</html>
the problem is i still dont get how stuff work in js. above the same it will load my js randomly as its async* assuming its all alert('this is x.js loaded') inside the 1-5.js
something like
// check goes randomly here
1.js or 1 js loaded
3.js 2 js loaded
2.js 3 js loaded
4.js 4 js loaded
// it should be here
so the logic is when my all js is load if (loaded['js'].length == o["js"].length) {alert('problem solved')}; should work. but it doent attach to the event somehow.
How can we check if all my js is loaded?
Looks like your check to see if they are all loaded is being run at the end of the loader function, so it will run immediately the async calls have been started. You need to move that check part into the the callback function thats passed to the .each function.
I ran into problems under IE 6 with a large JavaScript heavy app where occasionally external script loading was aborted without any discernable network trouble, so I ended up doing
<script src="sourcefile-1.js"></script>
...
<script src="sourcefile-n.js"></script>
<script src="last-loaded.js"></script>
<body onload="if(!window.allLoaded){/*reload*/}">...</body>
where last-loaded.js just did
window.allLoaded = true;
and where the reload code would redirect to an error page if a reload hadn't fixed the problem after a few tries.
This isn't the dynamic loader problem, but a similar approach should work with a dynamic loader as long as you can identify a point after which all external code should have loaded and you can run a very simple inlined script at that point.
Related
I have a local JS file that needs to be called using a script object. However, I am not able to get the functions to run. Here's the code snippet.
<script type="text/javascript">
var x = document.createElement("SCRIPT");
x.type = "text/javascript";
x.src = "file:///C:/scripts/localscript.js";
//one of the functions is loadData();
loadData(); //I'm getting reference error, loadData is not defined.
</script>
Thank you,
You need to create a script element and insert it in DOM (mostly under head) to load the script. When that script is loaded by the browser, whatever you return from that script will be available.
Consider sampleScript.js with below code
(function(window){
'use strict';
window.app = {
sayHi: function() {
console.log('Hey there !');
}
};
})(this);
To load this script, I do
<script>
var node = document.createElement('script');
node.src = 'sampleScript.js';
node.addEventListener('load', onScriptLoad, false);
node.async = true;
document.head.appendChild(node);
function onScriptLoad(evt) {
console.log('Script loaded.');
console.log('app.sayHi ---> ');
app && app.sayHi();
}
</script>
Taking cues, you can fit to your need. Hope this helps.
Use the onload event:
x.onload = function() { window.loadData(); }
I need to add jquery and then another script that relies on jquery.
I then need to have code that uses both assets but my problem is that i don't want my code to run until i know that both assets are loaded.
I think the process would be to load jquery and then wait until jquery is loaded by waiting for window.onload, then load the jquery plugin, then detect that the plugin has loaded, then load my own code that uses functions from the jquery plugin.
code so far:
// load jquery if it is not allready loaded and put it into no conflict mode so the $ is available for other librarys that might be allready on the page.
if(!window.jQuery) {
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
jQuery.noConflict(); // stop jquery eating the $
console.log("added jquery");
}
window.onload = function(e) {
// we know that jquery should be available now as the window has loaded
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) { // use jquery to ask if the plugins function is allready on the page (don't do this if the website already had the plugin)
// website didn't have the plugin so add it to the page.
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
}
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) {
// console.log("serializeObject is undefined");
// its going to be undefined here because Its still loading in the script
} else {
// console.log("we have serializeObject");
}
// I now dont know when to call my code that uses .serializeObject() because it could still be loading
// my code
var form_data_object = jQuery('form#mc-embedded-subscribe-form').serializeObject();
};
You have to do like
Include
<script type="text/javascript" id="AssetJS"></script>
Script
$("#AssetJS").attr("src", "Asset.js");
$("#AssetJS").load(function () {
//after loaded jquery asset do your code here
})
OK i managed to find another way that is working for my specific needs so I am answering my own question.
Using this function from http://www.sitepoint.com/dynamically-load-jquery-library-javascript/
function loadScript(url, callback) {
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
and usage in my case:
if(!window.jQuery) {
loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js", function () {
jQuery.noConflict(); // stop jquery eating the $
console.log('jquery loaded');
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) { // use jquery to ask if the plugins function is already on the page
loadScript("https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js", function () {
console.log('serialize loaded');
SURGE_start(); // both scrips where not on the website but have now been added so lets run my code now.
});
}
});
} else {
if ( !jQuery.isFunction(jQuery.fn.serializeObject) ) { // use jquery to ask if the plugins function is already on the page
loadScript("https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js", function () {
console.log('serialize loaded');
SURGE_start(); // jquery was on the web page but the plugin was not included. now we have both scripts lets run my code.
});
} else {
SURGE_start(); // web page already had both scripts so just run my code.
}
}
An easy way is using headjs. It's working fine on several projects.
I have the below code is working as long as I load the employee section and its scripts as part of the index.html
index.html Working Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="style.css" />
<script data-require="jquery" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<h1>Employee Details loaded as usual and sript is executing able to see alert</h1>
</body>
</html>
Script.js
var empModule = (function (empModule) {
var Years = null;
$(document).ready(function () {
var empdata = { Name: 'Billa' }
var myVM = new empModule.viewModel(empdata);
});
empModule.viewModel = function (data) {
var that = this;
that.Name = data.Name;
alert(that.Name);
};
return {
empModule: empModule
}
} (empModule || {}));
Error Scenario:
We decide to move employee related section based on some condition. Hence we are loading this section and section related script(emp.js) via Ajax. But now it is throwing the error empModule.viewModel is not a constructor. Why is it so?
If I move the document.ready section at the bottom like the below order, it is working
Emp.js(moved from script.js to emp.js and load via ajax(
var empModule = (function (empModule) {
var Years = null;
// Code not working when we load this script file using Ajax.
// But works if we move this document.ready at bottom
//$(document).ready(function () {
// var empdata = { Name: 'Billa' }
// var myVM = new empModule.viewModel(empdata);
//});
empModule.viewModel = function (data) {
var that = this;
that.Name = data.Name;
alert(that.Name);
};
//Working only if I keep the ready section here
$(document).ready(function () {
var empdata = { Name: 'Billa' }
var myVM = new empModule.viewModel(empdata);
});
return {
empModule: empModule
}
} (empModule || {}));
The function empModule will get executed automatically as it is self
executing function. When it is executing it needs to prepare a
empModule.viewModel object, but failed to do that when viewModel
definition is located after document.ready (caller). This happens only
when I load this script via Ajax, but works if I pre load it in a page
This is because in the first example the script.js is part of the document and therefore the document.ready waits for that .js file to be loaded before trying to call empModule.viewModel().
In the second example, your script is loaded asyncronously, but the page is not taking this into account. So the page loads (without the script.js) and then you load the script.
At this point, the document is ready (as the ajax loaded script isn't part of the document) so the empModule.viewModel() call fires straight away, before the rest of the script (which is the bit that defines the function) and you get your error.
Just like #dougajmcdonald said is your issue, but your solution is, instead of loadding by AJAX, just insert the script tag in your document:
// Instead of $.ajax(... /myScript.js) use:
function loadMyScript(){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = 'myScript.js';
script.onload = function() {
empModule.doSomething(); // or callback
};
}
Situation:
jQuery is dynamically loaded together with other scripts by one file javascripts.js in the <head> section of the html file
Each html file has it's own javascript code executed on jQuery(document).ready() in the <body> section of the html file
Problem:
Error: jQuery is not defined for javascript in the <body> section
Modifying the html file is not an option (+1000 files with same problem)
Example html file:
<html>
<head>
<title>JS test</title>
<script src="javascripts.js" type="text/javascript"></script>
</head>
<body>
<input type="text" class="date">
<script>
jQuery(document).ready(function() { // Error: jQuery not defined
jQuery('.date').datepicker();
});
</script>
</body>
</html>
javascripts.js:
// Load jQuery before any other javascript file
function loadJS(src, callback) {
var s = document.createElement('script');
s.src = src;
s.async = true;
s.onreadystatechange = s.onload = function() {
var state = s.readyState;
console.log("state: "+state);
if (!callback.done && (!state || /loaded|complete/.test(state))) {
callback.done = true;
callback();
}
};
document.getElementsByTagName('head')[0].appendChild(s);
}
loadJS('javascripts/jquery-1.8.3.min.js', function() {
var files = Array(
'javascripts/functions.js',
'javascripts/settings.js'
);
if (document.getElementsByTagName && document.createElement) {
var head = document.getElementsByTagName('head')[0];
for (i = 0; i < files.length; i++) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', files[i]);
script.async = true;
head.appendChild(script);
}
}
});
This is happening, as many in the comments have pointed out, because you are loading jQuery asynchronously. Asynchronous means the rest of the code is executed, and so your document-ready handler (DRH) line is running before jQuery is present in the environment.
Here's a really hacky way of resolving this. It involves making a temporary substitute of jQuery whose job is just to log the DRH callbacks until jQuery has arrived. When it does, we pass them in turn to jQuery.
JS:
//temporary jQuery substitute - just log incoming DRH callbacks
function jQuery(func) {
if (func) drh_callbacks.push(func);
return {ready: function(func) { drh_callbacks.push(func); }};
};
var $ = jQuery, drh_callbacks = [];
//asynchronously load jQuery
setTimeout(function() {
var scr = document.createElement('script');
scr.src = '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js';
document.head.appendChild(scr);
scr.onload = function() {
$.each(drh_callbacks, function(i, func) { $(func); });
};
}, 2000);
HTML:
jQuery(document).ready(function() { alert('jQuery has loaded!'); });
Fiddle: http://jsfiddle.net/y7aE3/
Note in this example drh_callbacks is global, which is obviously bad. Ideally hook it onto a namespace or something, e.g. mynamespace.drh_callbacks.
I believe this simple solution should do the trick. The changed line in the html changes the jquery onload function to a regular function. The jquery onload function will sometimes happen before the jquery is loaded and we can't have that. It's unreliable. We need that function not to execute on page load, but AFTER the jquery has loaded.
To that end, the three lines I've added in the javascript.js are inside the code that is executed immediately after jQuery has finished loading. They test to see if the pageLoaded function has been defined (so you don't have to put one on every page, only the ones that need it) and then execute it if it's there.
Now, because the change to the HTML is simple, you can just do a regex search and replace on those 1000 files to fix them. Tools like Sublime, Eclipse or TextPad are suited for that task.
Cheers!
Example html file:
<html>
<head>
<title>JS test</title>
<script src="javascripts.js" type="text/javascript"></script>
</head>
<body>
<input type="text" class="date">
<script>
function pageLoaded() { // changed
jQuery('.date').datepicker();
} // changed
</script>
</body>
</html>
javascripts.js:
// Load jQuery before any other javascript file
function loadJS(src, callback) {
var s = document.createElement('script');
s.src = src;
s.async = true;
s.onreadystatechange = s.onload = function() {
var state = s.readyState;
console.log("state: "+state);
if (!callback.done && (!state || /loaded|complete/.test(state))) {
callback.done = true;
callback();
}
};
document.getElementsByTagName('head')[0].appendChild(s);
}
loadJS('javascripts/jquery-1.8.3.min.js', function() {
var files = Array(
'javascripts/functions.js',
'javascripts/settings.js'
);
if (document.getElementsByTagName && document.createElement) {
var head = document.getElementsByTagName('head')[0];
for (i = 0; i < files.length; i++) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', files[i]);
script.async = true;
head.appendChild(script);
}
}
if( typeof(pageLoaded) == "function" ){ // added
pageLoaded(); // added
} // added
});
You should try following workaround to load scripts synchronously:
function loadJS(src, callback) {
document.write('<scr'+'ipt src="'+src+'"><\/scr'+'ipt>');
callback();
}
IMPORTANT to note: this function should be called always before DOM is fully rendered.
I have a question and may be you can help me.
I have a page (index.php):
...
<head>
<script src="site/js/mootools-core.js" /></script>
<script src="site/js/mootools-more.js" /></script>
<script type="text/javascript" src="site/js/router.js" /></script>
<script type="text/javascript" src="site/js/app.js" /></script>
</head>
<body></body>
...
router.js project: https://github.com/xrado/Router/blob/master/Demo/index.html
app.js:
window.addEvent('domready',function(){
var Core = {
loadScript:function(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
}
} else { //Others
script.onload = function(){
callback();
}
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
}
var router = new Router({
routes: {
'' : 'index',
'#!index' : 'index'
},
onReady: function(){
Core.init(); // Render body here ... working!
},
onIndex: function() {
Core.loadScript("index.js", function(){
index.init(); // ReferenceError: index is not defined
console.log('Index loaded...');
});
}
});
});
Here is my external index.js:
var index = {
init : function(){
$('div_content').set('html', index.render());
},
render: function() {
...
}
}
If I tried to load my page I get this error:
ReferenceError: index is not defined
index.init();
Can't find an error two weeks!
Thanks.
that works for me:
<html>
<head>
<script>
document.addEventListener("DOMContentLoaded",function(){
var Core = {
loadScript:function(url, callback){
var script = document.createElement("script");
script.type = "text/javascript";
script.onload = function(){
callback();
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
}
Core.loadScript("index.js", function(){
index.init(); // ReferenceError: index is not defined
console.log('Index loaded...');
});
});
</script>
</head>
<body>
</body>
</html>
My assumption would be that as "window.addEvent("domready"...." is no native method, mooTools does some magic and changes the scope for loaded variables. So just try out document.addEventListener("DOMContentLoaded",function(){
HTH
Gekkstah
haha, i see you are using my and xrado's Router class. :) in any case, the problem you have is in your init and javascript loader.
first off, this:
var index = {
init : function(){
$('div_content').set('html', index.render());
},
render: function() {
...
}
}
when running init methods, this will be the host object so this.render()
second, lazy loading re-invented. see the mootools Assets.js - https://github.com/mootools/mootools-more/blob/master/Source/Utilities/Assets.js#L24-48
there's an error in the mootools source which means IE9 will revert to onreadystatechange
callback despite of IE9 already supporting load event. still, it's fine, it won't ever fire before it's ready. keep in mind the scope of index when brought in later.
I tend to also use RequireJS, which allows me to lazy-load resources in time for the app to work, it's a nice way of delegating stuff and being able to package them
blatant self advert
Since your index.js looks like a view, I would recommend you look at Epitome, which I wrote to be able to structure such apps in MooTools - http://dimitarchristoff.github.com/Epitome/ - it's a lightweight MVP implementation based upon Class and Events. Comes with the same router class, works with AMD or normal script loading, etc. And has a proper view / model / collection / templating structure.
For example, look at the todomvc implementation of that in a hash app here: http://dimitarchristoff.github.com/Epitome/#examples/todomvc-reference - see the repo with example code https://github.com/DimitarChristoff/Epitome-todo.